JAL-3205 added a delta parameter to Matrix.equals()
[jalview.git] / src / jalview / appletgui / IdCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.appletgui;
22
23 import jalview.datamodel.SequenceI;
24 import jalview.viewmodel.ViewportListenerI;
25 import jalview.viewmodel.ViewportRanges;
26
27 import java.awt.Color;
28 import java.awt.Font;
29 import java.awt.Graphics;
30 import java.awt.Image;
31 import java.awt.Panel;
32 import java.beans.PropertyChangeEvent;
33 import java.util.List;
34
35 public class IdCanvas extends Panel implements ViewportListenerI
36 {
37   protected AlignViewport av;
38
39   protected boolean showScores = true;
40
41   protected int maxIdLength = -1;
42
43   protected String maxIdStr = null;
44
45   Image image;
46
47   Graphics gg;
48
49   int imgHeight = 0;
50
51   boolean fastPaint = false;
52
53   List<SequenceI> searchResults;
54
55   public IdCanvas(AlignViewport av)
56   {
57     setLayout(null);
58     this.av = av;
59     PaintRefresher.Register(this, av.getSequenceSetId());
60     av.getRanges().addPropertyChangeListener(this);
61   }
62
63   public void drawIdString(Graphics gg, boolean hiddenRows, SequenceI s,
64           int i, int starty, int ypos)
65   {
66     int charHeight = av.getCharHeight();
67
68     if (searchResults != null && searchResults.contains(s))
69     {
70       gg.setColor(Color.black);
71       gg.fillRect(0, ((i - starty) * charHeight) + ypos, getSize().width,
72               charHeight);
73       gg.setColor(Color.white);
74     }
75     else if (av.getSelectionGroup() != null
76             && av.getSelectionGroup().getSequences(null).contains(s))
77     {
78       gg.setColor(Color.lightGray);
79       gg.fillRect(0, ((i - starty) * charHeight) + ypos, getSize().width,
80               charHeight);
81       gg.setColor(Color.white);
82     }
83     else
84     {
85       gg.setColor(av.getSequenceColour(s));
86       gg.fillRect(0, ((i - starty) * charHeight) + ypos, getSize().width,
87               charHeight);
88       gg.setColor(Color.black);
89     }
90
91     gg.drawString(s.getDisplayId(av.getShowJVSuffix()), 0,
92             ((i - starty) * charHeight) + ypos + charHeight
93                     - (charHeight / 5));
94
95     if (hiddenRows)
96     {
97       drawMarker(i, starty, ypos);
98     }
99
100   }
101
102   public void fastPaint(int vertical)
103   {
104     if (gg == null || av.getWrapAlignment())
105     {
106       repaint();
107       return;
108     }
109
110     ViewportRanges ranges = av.getRanges();
111
112     gg.copyArea(0, 0, getSize().width, imgHeight, 0,
113             -vertical * av.getCharHeight());
114
115     int ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transY = 0;
116     if (vertical > 0) // scroll down
117     {
118       ss = es - vertical;
119       if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
120                                      // at a
121       // time
122       {
123         ss = ranges.getStartSeq();
124       }
125       else
126       {
127         transY = imgHeight - ((vertical + 1) * av.getCharHeight());
128       }
129     }
130     else if (vertical < 0)
131     {
132       es = ss - vertical;
133       if (es > ranges.getEndSeq())
134       {
135         es = ranges.getEndSeq();
136       }
137     }
138
139     gg.translate(0, transY);
140
141     drawIds(ss, es);
142
143     gg.translate(0, -transY);
144
145     fastPaint = true;
146     repaint();
147   }
148
149   @Override
150   public void update(Graphics g)
151   {
152     paint(g);
153   }
154
155   @Override
156   public void paint(Graphics g)
157   {
158     if (getSize().height < 0 || getSize().width < 0)
159     {
160       return;
161     }
162     if (fastPaint)
163     {
164       fastPaint = false;
165       g.drawImage(image, 0, 0, this);
166       return;
167     }
168
169     imgHeight = getSize().height;
170     imgHeight -= imgHeight % av.getCharHeight();
171
172     if (imgHeight < 1)
173     {
174       return;
175     }
176
177     if (image == null || imgHeight != image.getHeight(this))
178     {
179       image = createImage(getSize().width, imgHeight);
180       gg = image.getGraphics();
181       gg.setFont(av.getFont());
182     }
183
184     // Fill in the background
185     gg.setColor(Color.white);
186     Font italic = new Font(av.getFont().getName(), Font.ITALIC,
187             av.getFont().getSize());
188     gg.setFont(italic);
189
190     gg.fillRect(0, 0, getSize().width, getSize().height);
191     drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq());
192     g.drawImage(image, 0, 0, this);
193   }
194
195   /**
196    * local copy of av.getCharHeight set at top of drawIds
197    */
198   private int avcharHeight;
199
200   void drawIds(int starty, int endy)
201   {
202     avcharHeight = av.getCharHeight();
203
204     Color currentColor = Color.white;
205     Color currentTextColor = Color.black;
206
207     final boolean doHiddenCheck = av.isDisplayReferenceSeq()
208             || av.hasHiddenRows();
209     boolean hiddenRows = av.hasHiddenRows() && av.getShowHiddenMarkers();
210
211     if (av.getWrapAlignment())
212     {
213       drawIdsWrapped(starty, doHiddenCheck, hiddenRows);
214       return;
215     }
216
217     // Now draw the id strings
218     SequenceI seq;
219     for (int i = starty; i <= endy; i++)
220     {
221       seq = av.getAlignment().getSequenceAt(i);
222       if (seq == null)
223       {
224         continue;
225       }
226       // hardwired italic IDs in applet currently
227       Font italic = new Font(av.getFont().getName(), Font.ITALIC,
228               av.getFont().getSize());
229       gg.setFont(italic);
230       // boolean isrep=false;
231       if (doHiddenCheck)
232       {
233         // isrep =
234         setHiddenFont(seq);
235       }
236
237       // Selected sequence colours
238       if ((searchResults != null) && searchResults.contains(seq))
239       {
240         currentColor = Color.black;
241         currentTextColor = Color.white;
242       }
243       else if ((av.getSelectionGroup() != null)
244               && av.getSelectionGroup().getSequences(null).contains(seq))
245       {
246         currentColor = Color.lightGray;
247         currentTextColor = Color.black;
248       }
249       else
250       {
251         currentColor = av.getSequenceColour(seq);
252         currentTextColor = Color.black;
253       }
254
255       gg.setColor(currentColor);
256       // TODO: isrep could be used to highlight the representative in a
257       // different way
258       gg.fillRect(0, (i - starty) * avcharHeight, getSize().width,
259               avcharHeight);
260       gg.setColor(currentTextColor);
261
262       gg.drawString(seq.getDisplayId(av.getShowJVSuffix()), 0,
263               (((i - starty) * avcharHeight) + avcharHeight)
264                       - (avcharHeight / 5));
265
266       if (hiddenRows)
267       {
268         drawMarker(i, starty, 0);
269       }
270     }
271   }
272
273   /**
274    * Draws sequence ids in wrapped mode
275    * 
276    * @param starty
277    * @param doHiddenCheck
278    * @param hiddenRows
279    */
280   protected void drawIdsWrapped(int starty, final boolean doHiddenCheck,
281           boolean hiddenRows)
282   {
283     int maxwidth = av.getAlignment().getWidth();
284     int alheight = av.getAlignment().getHeight();
285
286     if (av.hasHiddenColumns())
287     {
288       maxwidth = av.getAlignment().getHiddenColumns()
289               .absoluteToVisibleColumn(maxwidth) - 1;
290     }
291
292     int annotationHeight = 0;
293     AnnotationLabels labels = null;
294
295     if (av.isShowAnnotation())
296     {
297       AnnotationPanel ap = new AnnotationPanel(av);
298       annotationHeight = ap.adjustPanelHeight();
299       labels = new AnnotationLabels(av);
300     }
301     int hgap = avcharHeight;
302     if (av.getScaleAboveWrapped())
303     {
304       hgap += avcharHeight;
305     }
306
307     int cHeight = alheight * avcharHeight + hgap + annotationHeight;
308
309     int rowSize = av.getRanges().getViewportWidth();
310
311     // hardwired italic IDs in applet currently
312     Font italic = new Font(av.getFont().getName(), Font.ITALIC,
313             av.getFont().getSize());
314     gg.setFont(italic);
315
316     /*
317      * draw repeating sequence ids until out of sequence data or
318      * out of visible space, whichever comes first
319      */
320     int ypos = hgap;
321     int row = av.getRanges().getStartRes();
322     while ((ypos <= getHeight()) && (row < maxwidth))
323     {
324       for (int i = starty; i < alheight; i++)
325       {
326
327         SequenceI s = av.getAlignment().getSequenceAt(i);
328         // gg.setFont(italic);
329         if (doHiddenCheck)
330         {
331           setHiddenFont(s);
332         }
333         drawIdString(gg, hiddenRows, s, i, 0, ypos);
334       }
335
336       if (labels != null)
337       {
338         gg.translate(0, ypos + (alheight * avcharHeight));
339         labels.drawComponent(gg, getSize().width);
340         gg.translate(0, -ypos - (alheight * avcharHeight));
341       }
342       ypos += cHeight;
343       row += rowSize;
344     }
345   }
346
347   public void setHighlighted(List<SequenceI> list)
348   {
349     searchResults = list;
350     repaint();
351   }
352
353   void drawMarker(int i, int starty, int yoffset)
354   {
355     SequenceI[] hseqs = av.getAlignment()
356             .getHiddenSequences().hiddenSequences;
357     // Use this method here instead of calling hiddenSeq adjust
358     // 3 times.
359     int hSize = hseqs.length;
360
361     int hiddenIndex = i;
362     int lastIndex = i - 1;
363     int nextIndex = i + 1;
364
365     for (int j = 0; j < hSize; j++)
366     {
367       if (hseqs[j] != null)
368       {
369         if (j - 1 < hiddenIndex)
370         {
371           hiddenIndex++;
372         }
373         if (j - 1 < lastIndex)
374         {
375           lastIndex++;
376         }
377         if (j - 1 < nextIndex)
378         {
379           nextIndex++;
380         }
381       }
382     }
383
384     boolean below = (hiddenIndex > lastIndex + 1);
385     boolean above = (nextIndex > hiddenIndex + 1);
386
387     gg.setColor(Color.blue);
388     if (below)
389     {
390       gg.fillPolygon(
391               new int[]
392               { getSize().width - avcharHeight,
393                   getSize().width - avcharHeight, getSize().width },
394               new int[]
395               { (i - starty) * avcharHeight + yoffset,
396                   (i - starty) * avcharHeight + yoffset + avcharHeight / 4,
397                   (i - starty) * avcharHeight + yoffset },
398               3);
399     }
400     if (above)
401     {
402       gg.fillPolygon(
403               new int[]
404               { getSize().width - avcharHeight,
405                   getSize().width - avcharHeight, getSize().width },
406               new int[]
407               { (i - starty + 1) * avcharHeight + yoffset,
408                   (i - starty + 1) * avcharHeight + yoffset
409                           - avcharHeight / 4,
410                   (i - starty + 1) * avcharHeight + yoffset },
411               3);
412
413     }
414   }
415
416   boolean setHiddenFont(SequenceI seq)
417   {
418     Font bold = new Font(av.getFont().getName(), Font.BOLD,
419             av.getFont().getSize());
420
421     if (av.isReferenceSeq(seq) || av.isHiddenRepSequence(seq))
422     {
423       gg.setFont(bold);
424       return true;
425     }
426     return false;
427   }
428
429   /**
430    * Respond to viewport range changes (e.g. alignment panel was scrolled). Both
431    * scrolling and resizing change viewport ranges. Scrolling changes both start
432    * and end points, but resize only changes end values. Here we only want to
433    * fastpaint on a scroll, with resize using a normal paint, so scroll events
434    * are identified as changes to the horizontal or vertical start value.
435    * <p>
436    * In unwrapped mode, only responds to a vertical scroll, as horizontal scroll
437    * leaves sequence ids unchanged. In wrapped mode, only vertical scroll is
438    * provided, but it generates a change of "startres" which does require an
439    * update here.
440    */
441   @Override
442   public void propertyChange(PropertyChangeEvent evt)
443   {
444     String propertyName = evt.getPropertyName();
445     if (propertyName.equals(ViewportRanges.STARTSEQ)
446             || (av.getWrapAlignment()
447                     && propertyName.equals(ViewportRanges.STARTRES)))
448     {
449       fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
450     }
451     else if (propertyName.equals(ViewportRanges.STARTRESANDSEQ))
452     {
453       fastPaint(((int[]) evt.getNewValue())[1]
454               - ((int[]) evt.getOldValue())[1]);
455     }
456     else if (propertyName.equals(ViewportRanges.MOVE_VIEWPORT))
457     {
458       repaint();
459     }
460   }
461 }