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