Hidden representatives moved from sequence to viewport
[jalview.git] / src / jalview / gui / TreeCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.gui;
20
21 import jalview.analysis.*;
22
23 import jalview.datamodel.*;
24
25 import jalview.schemes.*;
26
27 import jalview.util.*;
28
29 import java.awt.*;
30 import java.awt.event.*;
31 import java.awt.print.*;
32
33 import java.util.*;
34
35 import javax.swing.*;
36
37
38 /**
39  * DOCUMENT ME!
40  *
41  * @author $author$
42  * @version $Revision$
43  */
44 public class TreeCanvas extends JPanel implements MouseListener, Runnable,
45     Printable, MouseMotionListener
46 {
47   /** DOCUMENT ME!! */
48   public static final String PLACEHOLDER = " * ";
49   NJTree tree;
50   JScrollPane scrollPane;
51   TreePanel tp;
52   AlignViewport av;
53   AlignmentPanel ap;
54   Font font;
55   FontMetrics fm;
56   boolean fitToWindow = true;
57   boolean showDistances = false;
58   boolean showBootstrap = false;
59   boolean markPlaceholders = false;
60   int offx = 20;
61   int offy;
62   float threshold;
63   String longestName;
64   int labelLength = -1;
65
66   Hashtable nameHash = new Hashtable();
67   Hashtable nodeHash = new Hashtable();
68   SequenceNode highlightNode;
69
70   boolean applyToAllViews = false;
71
72   /**
73    * Creates a new TreeCanvas object.
74    *
75    * @param av DOCUMENT ME!
76    * @param tree DOCUMENT ME!
77    * @param scroller DOCUMENT ME!
78    * @param label DOCUMENT ME!
79    */
80   public TreeCanvas(TreePanel tp,
81                     AlignmentPanel ap,
82                     JScrollPane scroller)
83   {
84     this.tp = tp;
85     this.av = ap.av;
86     this.ap = ap;
87     font = av.getFont();
88     scrollPane = scroller;
89     addMouseListener(this);
90     addMouseMotionListener(this);
91     ToolTipManager.sharedInstance().registerComponent(this);
92   }
93
94   /**
95    * DOCUMENT ME!
96    *
97    * @param sequence DOCUMENT ME!
98    */
99   public void treeSelectionChanged(SequenceI sequence)
100   {
101     AlignmentPanel[] aps = getAssociatedPanels();
102
103     for (int a = 0; a < aps.length; a++)
104     {
105       SequenceGroup selected = aps[a].av.getSelectionGroup();
106
107       if (selected == null)
108       {
109         selected = new SequenceGroup();
110         aps[a].av.setSelectionGroup(selected);
111       }
112
113       selected.setEndRes(aps[a].av.alignment.getWidth() - 1);
114       selected.addOrRemove(sequence, true);
115     }
116   }
117
118   /**
119    * DOCUMENT ME!
120    *
121    * @param tree DOCUMENT ME!
122    */
123   public void setTree(NJTree tree)
124   {
125     this.tree = tree;
126     tree.findHeight(tree.getTopNode());
127
128     // Now have to calculate longest name based on the leaves
129     Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
130     boolean has_placeholders = false;
131     longestName = "";
132
133     for (int i = 0; i < leaves.size(); i++)
134     {
135       SequenceNode lf = (SequenceNode) leaves.elementAt(i);
136
137       if (lf.isPlaceholder())
138       {
139         has_placeholders = true;
140       }
141
142       if (longestName.length() < ( (Sequence) lf.element()).getName()
143           .length())
144       {
145         longestName = TreeCanvas.PLACEHOLDER +
146             ( (Sequence) lf.element()).getName();
147       }
148     }
149
150     setMarkPlaceholders(has_placeholders);
151   }
152
153   /**
154    * DOCUMENT ME!
155    *
156    * @param g DOCUMENT ME!
157    * @param node DOCUMENT ME!
158    * @param chunk DOCUMENT ME!
159    * @param scale DOCUMENT ME!
160    * @param width DOCUMENT ME!
161    * @param offx DOCUMENT ME!
162    * @param offy DOCUMENT ME!
163    */
164   public void drawNode(Graphics g, SequenceNode node, float chunk,
165                        float scale, int width, int offx, int offy)
166   {
167     if (node == null)
168     {
169       return;
170     }
171
172     if ( (node.left() == null) && (node.right() == null))
173     {
174       // Drawing leaf node
175       float height = node.height;
176       float dist = node.dist;
177
178       int xstart = (int) ( (height - dist) * scale) + offx;
179       int xend = (int) (height * scale) + offx;
180
181       int ypos = (int) (node.ycount * chunk) + offy;
182
183       if (node.element() instanceof SequenceI)
184       {
185         SequenceI seq = (SequenceI) ( (SequenceNode) node).element();
186
187         if (av.getSequenceColour(seq) == Color.white)
188         {
189           g.setColor(Color.black);
190         }
191         else
192         {
193           g.setColor(av.getSequenceColour(seq).darker());
194         }
195       }
196       else
197       {
198         g.setColor(Color.black);
199       }
200
201       // Draw horizontal line
202       g.drawLine(xstart, ypos, xend, ypos);
203
204       String nodeLabel = "";
205
206       if (showDistances && (node.dist > 0))
207       {
208         nodeLabel = new Format("%-.2f").form(node.dist);
209       }
210
211       if (showBootstrap)
212       {
213         if (showDistances)
214         {
215           nodeLabel = nodeLabel + " : ";
216         }
217
218         nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
219       }
220
221       if (!nodeLabel.equals(""))
222       {
223         g.drawString(nodeLabel, xstart + 2, ypos - 2);
224       }
225
226       String name = (markPlaceholders && node.isPlaceholder())
227           ? (PLACEHOLDER + node.getName()) : node.getName();
228
229       int charWidth = fm.stringWidth(name) + 3;
230       int charHeight = font.getSize();
231
232       Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
233                                      charWidth, charHeight);
234
235       nameHash.put( (SequenceI) node.element(), rect);
236
237       // Colour selected leaves differently
238       SequenceGroup selected = av.getSelectionGroup();
239
240       if ( (selected != null) &&
241           selected.getSequences(null).contains( (SequenceI) node.element()))
242       {
243         g.setColor(Color.gray);
244
245         g.fillRect(xend + 10, ypos - charHeight / 2, charWidth,
246                    charHeight);
247         g.setColor(Color.white);
248       }
249
250       g.drawString(name, xend + 10, ypos + fm.getDescent());
251       g.setColor(Color.black);
252     }
253     else
254     {
255       drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
256                offy);
257       drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
258                offy);
259
260       float height = node.height;
261       float dist = node.dist;
262
263       int xstart = (int) ( (height - dist) * scale) + offx;
264       int xend = (int) (height * scale) + offx;
265       int ypos = (int) (node.ycount * chunk) + offy;
266
267       g.setColor( ( (SequenceNode) node).color.darker());
268
269       // Draw horizontal line
270       g.drawLine(xstart, ypos, xend, ypos);
271       if (node == highlightNode)
272         g.fillRect(xend - 3, ypos - 3, 6, 6);
273       else
274         g.fillRect(xend - 2, ypos - 2, 4, 4);
275
276       int ystart = (int) ( ( (SequenceNode) node.left()).ycount * chunk) +
277           offy;
278       int yend = (int) ( ( (SequenceNode) node.right()).ycount * chunk) +
279           offy;
280
281       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
282       nodeHash.put(node, pos);
283
284       g.drawLine( (int) (height * scale) + offx, ystart,
285                  (int) (height * scale) + offx, yend);
286
287       if (showDistances && (node.dist > 0))
288       {
289         g.drawString(new Format("%-.2f").form(node.dist).trim(), xstart + 2,
290                      ypos - 2);
291       }
292     }
293   }
294
295   /**
296    * DOCUMENT ME!
297    *
298    * @param x DOCUMENT ME!
299    * @param y DOCUMENT ME!
300    *
301    * @return DOCUMENT ME!
302    */
303   public Object findElement(int x, int y)
304   {
305     Enumeration keys = nameHash.keys();
306
307     while (keys.hasMoreElements())
308     {
309       Object ob = keys.nextElement();
310       Rectangle rect = (Rectangle) nameHash.get(ob);
311
312       if ( (x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
313           (y <= (rect.y + rect.height)))
314       {
315         return ob;
316       }
317     }
318
319     keys = nodeHash.keys();
320
321     while (keys.hasMoreElements())
322     {
323       Object ob = keys.nextElement();
324       Rectangle rect = (Rectangle) nodeHash.get(ob);
325
326       if ( (x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
327           (y <= (rect.y + rect.height)))
328       {
329         return ob;
330       }
331     }
332
333     return null;
334   }
335
336   /**
337    * DOCUMENT ME!
338    *
339    * @param pickBox DOCUMENT ME!
340    */
341   public void pickNodes(Rectangle pickBox)
342   {
343     int width = getWidth();
344     int height = getHeight();
345
346     SequenceNode top = tree.getTopNode();
347
348     float wscale = (float) ( (width * .8) - (offx * 2)) / tree.getMaxHeight();
349
350     if (top.count == 0)
351     {
352       top.count = ( (SequenceNode) top.left()).count +
353           ( (SequenceNode) top.right()).count;
354     }
355
356     float chunk = (float) (height - (offy)) / top.count;
357
358     pickNode(pickBox, top, chunk, wscale, width, offx, offy);
359   }
360
361   /**
362    * DOCUMENT ME!
363    *
364    * @param pickBox DOCUMENT ME!
365    * @param node DOCUMENT ME!
366    * @param chunk DOCUMENT ME!
367    * @param scale DOCUMENT ME!
368    * @param width DOCUMENT ME!
369    * @param offx DOCUMENT ME!
370    * @param offy DOCUMENT ME!
371    */
372   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
373                        float scale, int width, int offx, int offy)
374   {
375     if (node == null)
376     {
377       return;
378     }
379
380     if ( (node.left() == null) && (node.right() == null))
381     {
382       float height = node.height;
383       float dist = node.dist;
384
385       int xstart = (int) ( (height - dist) * scale) + offx;
386       int xend = (int) (height * scale) + offx;
387
388       int ypos = (int) (node.ycount * chunk) + offy;
389
390       if (pickBox.contains(new Point(xend, ypos)))
391       {
392         if (node.element() instanceof SequenceI)
393         {
394           SequenceI seq = (SequenceI) node.element();
395           SequenceGroup sg = av.getSelectionGroup();
396
397           if (sg != null)
398           {
399             sg.addOrRemove(seq, true);
400           }
401         }
402       }
403     }
404     else
405     {
406       pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
407                offx, offy);
408       pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
409                offx, offy);
410     }
411   }
412
413   /**
414    * DOCUMENT ME!
415    *
416    * @param node DOCUMENT ME!
417    * @param c DOCUMENT ME!
418    */
419   public void setColor(SequenceNode node, Color c)
420   {
421     if (node == null)
422     {
423       return;
424     }
425
426     if ( (node.left() == null) && (node.right() == null))
427     {
428       node.color = c;
429
430       if (node.element() instanceof SequenceI)
431       {
432         AlignmentPanel[] aps = getAssociatedPanels();
433         for (int a = 0; a < aps.length; a++)
434         {
435           aps[a].av.setSequenceColour( (SequenceI) node.element(), c);
436         }
437       }
438     }
439     else
440     {
441       node.color = c;
442       setColor( (SequenceNode) node.left(), c);
443       setColor( (SequenceNode) node.right(), c);
444     }
445   }
446
447   /**
448    * DOCUMENT ME!
449    */
450   void startPrinting()
451   {
452     Thread thread = new Thread(this);
453     thread.start();
454   }
455
456   // put printing in a thread to avoid painting problems
457   public void run()
458   {
459     PrinterJob printJob = PrinterJob.getPrinterJob();
460     PageFormat pf = printJob.pageDialog(printJob.defaultPage());
461
462     printJob.setPrintable(this, pf);
463
464     if (printJob.printDialog())
465     {
466       try
467       {
468         printJob.print();
469       }
470       catch (Exception PrintException)
471       {
472         PrintException.printStackTrace();
473       }
474     }
475   }
476
477   /**
478    * DOCUMENT ME!
479    *
480    * @param pg DOCUMENT ME!
481    * @param pf DOCUMENT ME!
482    * @param pi DOCUMENT ME!
483    *
484    * @return DOCUMENT ME!
485    *
486    * @throws PrinterException DOCUMENT ME!
487    */
488   public int print(Graphics pg, PageFormat pf, int pi)
489       throws PrinterException
490   {
491     pg.setFont(font);
492     pg.translate( (int) pf.getImageableX(), (int) pf.getImageableY());
493
494     int pwidth = (int) pf.getImageableWidth();
495     int pheight = (int) pf.getImageableHeight();
496
497     int noPages = getHeight() / pheight;
498
499     if (pi > noPages)
500     {
501       return Printable.NO_SUCH_PAGE;
502     }
503
504     if (pwidth > getWidth())
505     {
506       pwidth = getWidth();
507     }
508
509     if (fitToWindow)
510     {
511       if (pheight > getHeight())
512       {
513         pheight = getHeight();
514       }
515
516       noPages = 0;
517     }
518     else
519     {
520       FontMetrics fm = pg.getFontMetrics(font);
521       int height = fm.getHeight() * nameHash.size();
522       pg.translate(0, -pi * pheight);
523       pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
524
525       // translate number of pages,
526       // height is screen size as this is the
527       // non overlapping text size
528       pheight = height;
529     }
530
531     draw(pg, pwidth, pheight);
532
533     return Printable.PAGE_EXISTS;
534   }
535
536   /**
537    * DOCUMENT ME!
538    *
539    * @param g DOCUMENT ME!
540    */
541   public void paintComponent(Graphics g)
542   {
543     super.paintComponent(g);
544     g.setFont(font);
545
546     if (tree == null)
547     {
548       g.drawString("Calculating tree....", 20, getHeight() / 2);
549     }
550     else
551     {
552       fm = g.getFontMetrics(font);
553
554       if (nameHash.size() == 0)
555       {
556         repaint();
557       }
558
559       if (fitToWindow ||
560           (!fitToWindow &&
561            (scrollPane.getHeight() > ( (fm.getHeight() * nameHash.size()) +
562                                       offy))))
563       {
564         draw(g, scrollPane.getWidth(), scrollPane.getHeight());
565         setPreferredSize(null);
566       }
567       else
568       {
569         setPreferredSize(new Dimension(scrollPane.getWidth(),
570                                        fm.getHeight() * nameHash.size()));
571         draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
572       }
573
574       scrollPane.revalidate();
575     }
576   }
577
578   /**
579    * DOCUMENT ME!
580    *
581    * @param fontSize DOCUMENT ME!
582    */
583   public void setFont(Font font)
584   {
585     this.font = font;
586     repaint();
587   }
588
589   /**
590    * DOCUMENT ME!
591    *
592    * @param g1 DOCUMENT ME!
593    * @param width DOCUMENT ME!
594    * @param height DOCUMENT ME!
595    */
596   public void draw(Graphics g1, int width, int height)
597   {
598     Graphics2D g2 = (Graphics2D) g1;
599     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
600                         RenderingHints.VALUE_ANTIALIAS_ON);
601     g2.setColor(Color.white);
602     g2.fillRect(0, 0, width, height);
603
604     g2.setFont(font);
605
606     offy = font.getSize() + 10;
607
608     fm = g2.getFontMetrics(font);
609
610     labelLength = fm.stringWidth(longestName) + 20; //20 allows for scrollbar
611
612     float wscale = (float) (width - labelLength - (offx * 2)) /
613         tree.getMaxHeight();
614
615     SequenceNode top = tree.getTopNode();
616
617     if (top.count == 0)
618     {
619       top.count = ( (SequenceNode) top.left()).count +
620           ( (SequenceNode) top.right()).count;
621     }
622
623     float chunk = (float) (height - (offy)) / top.count;
624
625     drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
626
627     if (threshold != 0)
628     {
629       if (av.getCurrentTree() == tree)
630       {
631         g2.setColor(Color.red);
632       }
633       else
634       {
635         g2.setColor(Color.gray);
636       }
637
638       int x = (int) ( (threshold * (float) (getWidth() - labelLength -
639                                             (2 * offx))) + offx);
640
641       g2.drawLine(x, 0, x, getHeight());
642     }
643   }
644
645   /**
646    * DOCUMENT ME!
647    *
648    * @param e DOCUMENT ME!
649    */
650   public void mouseReleased(MouseEvent e)
651   {
652   }
653
654   /**
655    * DOCUMENT ME!
656    *
657    * @param e DOCUMENT ME!
658    */
659   public void mouseEntered(MouseEvent e)
660   {
661   }
662
663   /**
664    * DOCUMENT ME!
665    *
666    * @param e DOCUMENT ME!
667    */
668   public void mouseExited(MouseEvent e)
669   {
670   }
671
672   /**
673    * DOCUMENT ME!
674    *
675    * @param e DOCUMENT ME!
676    */
677   public void mouseClicked(MouseEvent evt)
678   {
679     if (highlightNode != null)
680     {
681       if (SwingUtilities.isRightMouseButton(evt))
682       {
683         Color col = JColorChooser.showDialog(this, "Select Sub-Tree Colour",
684                                              highlightNode.color);
685
686         setColor(highlightNode, col);
687       }
688       else
689       if (evt.getClickCount() > 1)
690       {
691         tree.swapNodes(highlightNode);
692         tree.reCount(tree.getTopNode());
693         tree.findHeight(tree.getTopNode());
694       }
695       else
696       {
697         Vector leaves = new Vector();
698         tree.findLeaves(highlightNode, leaves);
699
700         for (int i = 0; i < leaves.size(); i++)
701         {
702           SequenceI seq =
703               (SequenceI) ( (SequenceNode) leaves.elementAt(i)).element();
704           treeSelectionChanged(seq);
705         }
706       }
707
708       PaintRefresher.Refresh(tp, av.getSequenceSetId());
709       repaint();
710     }
711   }
712
713   public void mouseMoved(MouseEvent evt)
714   {
715     av.setCurrentTree(tree);
716
717     Object ob = findElement(evt.getX(), evt.getY());
718
719     if (ob instanceof SequenceNode)
720     {
721       highlightNode = (SequenceNode) ob;
722       this.setToolTipText(
723           "<html>Left click to select leaves"
724           + "<br>Double-click to invert leaves"
725           + "<br>Right click to change colour");
726       repaint();
727
728     }
729     else
730     {
731       if (highlightNode != null)
732       {
733         highlightNode = null;
734         setToolTipText(null);
735         repaint();
736       }
737     }
738   }
739
740   public void mouseDragged(MouseEvent ect)
741   {}
742
743   /**
744    * DOCUMENT ME!
745    *
746    * @param e DOCUMENT ME!
747    */
748   public void mousePressed(MouseEvent e)
749   {
750     av.setCurrentTree(tree);
751
752     int x = e.getX();
753     int y = e.getY();
754
755     Object ob = findElement(x, y);
756
757     if (ob instanceof SequenceI)
758     {
759       treeSelectionChanged( (Sequence) ob);
760       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
761       repaint();
762       return;
763     }
764     else if (! (ob instanceof SequenceNode))
765     {
766       // Find threshold
767       if (tree.getMaxHeight() != 0)
768       {
769         threshold = (float) (x - offx) / (float) (getWidth() -
770                                                   labelLength - (2 * offx));
771
772         tree.getGroups().removeAllElements();
773         tree.groupNodes(tree.getTopNode(), threshold);
774         setColor(tree.getTopNode(), Color.black);
775
776         AlignmentPanel[] aps = getAssociatedPanels();
777
778         for (int a = 0; a < aps.length; a++)
779         {
780           aps[a].av.setSelectionGroup(null);
781           aps[a].av.alignment.deleteAllGroups();
782           aps[a].av.sequenceColours = null;
783         }
784         colourGroups();
785       }
786
787       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
788       repaint();
789     }
790
791   }
792
793   void colourGroups()
794   {
795     for (int i = 0; i < tree.getGroups().size(); i++)
796     {
797       Color col = new Color( (int) (Math.random() * 255),
798                             (int) (Math.random() * 255),
799                             (int) (Math.random() * 255));
800       setColor( (SequenceNode) tree.getGroups().elementAt(i),
801                col.brighter());
802
803       Vector l = tree.findLeaves( (SequenceNode) tree.getGroups()
804                                  .elementAt(i),
805                                  new Vector());
806
807       Vector sequences = new Vector();
808
809       for (int j = 0; j < l.size(); j++)
810       {
811         SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();
812
813         if (!sequences.contains(s1))
814         {
815           sequences.addElement(s1);
816         }
817       }
818
819       ColourSchemeI cs = null;
820
821       if (av.getGlobalColourScheme() != null)
822       {
823         if (av.getGlobalColourScheme() instanceof UserColourScheme)
824         {
825           cs = new UserColourScheme(
826               ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
827
828         }
829         else
830           cs = ColourSchemeProperty.getColour(sequences,
831                                               av.alignment.getWidth(),
832                                               ColourSchemeProperty.
833                                               getColourName(
834                                                   av.getGlobalColourScheme()));
835
836         cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
837                         av.getIgnoreGapsConsensus());
838       }
839
840       SequenceGroup sg = new SequenceGroup(sequences,
841                                            "TreeGroup", cs, true, true, false,
842                                            0,
843                                            av.alignment.getWidth() - 1);
844
845       AlignmentPanel[] aps = getAssociatedPanels();
846       for (int a = 0; a < aps.length; a++)
847       {
848         if (aps[a].av.getGlobalColourScheme() != null
849             && aps[a].av.getGlobalColourScheme().conservationApplied())
850         {
851           Conservation c = new Conservation("Group",
852                                             ResidueProperties.propHash, 3,
853                                             sg.getSequences(null),
854                                             sg.getStartRes(), sg.getEndRes());
855
856           c.calculate();
857           c.verdict(false, aps[a].av.ConsPercGaps);
858           sg.cs.setConservation(c);
859         }
860
861         aps[a].av.alignment.addGroup(sg);
862       }
863     }
864
865   }
866
867   /**
868    * DOCUMENT ME!
869    *
870    * @param state DOCUMENT ME!
871    */
872   public void setShowDistances(boolean state)
873   {
874     this.showDistances = state;
875     repaint();
876   }
877
878   /**
879    * DOCUMENT ME!
880    *
881    * @param state DOCUMENT ME!
882    */
883   public void setShowBootstrap(boolean state)
884   {
885     this.showBootstrap = state;
886     repaint();
887   }
888
889   /**
890    * DOCUMENT ME!
891    *
892    * @param state DOCUMENT ME!
893    */
894   public void setMarkPlaceholders(boolean state)
895   {
896     this.markPlaceholders = state;
897     repaint();
898   }
899
900   AlignmentPanel[] getAssociatedPanels()
901   {
902     if (applyToAllViews)
903     {
904       return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
905     }
906     else
907       return new AlignmentPanel[]{ap};
908   }
909 }