fix serious repetitive exceptions if user closes colour dialog box without selecting...
[jalview.git] / src / jalview / gui / TreeCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 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 java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25 import java.awt.print.*;
26 import javax.swing.*;
27
28 import jalview.analysis.*;
29 import jalview.datamodel.*;
30 import jalview.schemes.*;
31 import jalview.util.*;
32
33 /**
34  * DOCUMENT ME!
35  *
36  * @author $author$
37  * @version $Revision$
38  */
39 public class TreeCanvas
40     extends JPanel implements MouseListener, Runnable,
41     Printable, MouseMotionListener
42 {
43   /** DOCUMENT ME!! */
44   public static final String PLACEHOLDER = " * ";
45   NJTree tree;
46   JScrollPane scrollPane;
47   TreePanel tp;
48   AlignViewport av;
49   AlignmentPanel ap;
50   Font font;
51   FontMetrics fm;
52   boolean fitToWindow = true;
53   boolean showDistances = false;
54   boolean showBootstrap = false;
55   boolean markPlaceholders = false;
56   int offx = 20;
57   int offy;
58   float threshold;
59   String longestName;
60   int labelLength = -1;
61
62   Hashtable nameHash = new Hashtable();
63   Hashtable nodeHash = new Hashtable();
64   SequenceNode highlightNode;
65
66   boolean applyToAllViews = false;
67
68   /**
69    * Creates a new TreeCanvas object.
70    *
71    * @param av DOCUMENT ME!
72    * @param tree DOCUMENT ME!
73    * @param scroller DOCUMENT ME!
74    * @param label DOCUMENT ME!
75    */
76   public TreeCanvas(TreePanel tp,
77                     AlignmentPanel ap,
78                     JScrollPane scroller)
79   {
80     this.tp = tp;
81     this.av = ap.av;
82     this.ap = ap;
83     font = av.getFont();
84     scrollPane = scroller;
85     addMouseListener(this);
86     addMouseMotionListener(this);
87     ToolTipManager.sharedInstance().registerComponent(this);
88   }
89
90   /**
91    * DOCUMENT ME!
92    *
93    * @param sequence DOCUMENT ME!
94    */
95   public void treeSelectionChanged(SequenceI sequence)
96   {
97     AlignmentPanel[] aps = getAssociatedPanels();
98
99     for (int a = 0; a < aps.length; a++)
100     {
101       SequenceGroup selected = aps[a].av.getSelectionGroup();
102
103       if (selected == null)
104       {
105         selected = new SequenceGroup();
106         aps[a].av.setSelectionGroup(selected);
107       }
108
109       selected.setEndRes(aps[a].av.alignment.getWidth() - 1);
110       selected.addOrRemove(sequence, true);
111     }
112   }
113
114   /**
115    * DOCUMENT ME!
116    *
117    * @param tree DOCUMENT ME!
118    */
119   public void setTree(NJTree tree)
120   {
121     this.tree = tree;
122     tree.findHeight(tree.getTopNode());
123
124     // Now have to calculate longest name based on the leaves
125     Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
126     boolean has_placeholders = false;
127     longestName = "";
128
129     for (int i = 0; i < leaves.size(); i++)
130     {
131       SequenceNode lf = (SequenceNode) leaves.elementAt(i);
132
133       if (lf.isPlaceholder())
134       {
135         has_placeholders = true;
136       }
137
138       if (longestName.length() < ( (Sequence) lf.element()).getName()
139           .length())
140       {
141         longestName = TreeCanvas.PLACEHOLDER +
142             ( (Sequence) lf.element()).getName();
143       }
144     }
145
146     setMarkPlaceholders(has_placeholders);
147   }
148
149   /**
150    * DOCUMENT ME!
151    *
152    * @param g DOCUMENT ME!
153    * @param node DOCUMENT ME!
154    * @param chunk DOCUMENT ME!
155    * @param scale DOCUMENT ME!
156    * @param width DOCUMENT ME!
157    * @param offx DOCUMENT ME!
158    * @param offy DOCUMENT ME!
159    */
160   public void drawNode(Graphics g, SequenceNode node, float chunk,
161                        float scale, int width, int offx, int offy)
162   {
163     if (node == null)
164     {
165       return;
166     }
167
168     if ( (node.left() == null) && (node.right() == null))
169     {
170       // Drawing leaf node
171       float height = node.height;
172       float dist = node.dist;
173
174       int xstart = (int) ( (height - dist) * scale) + offx;
175       int xend = (int) (height * scale) + offx;
176
177       int ypos = (int) (node.ycount * chunk) + offy;
178
179       if (node.element() instanceof SequenceI)
180       {
181         SequenceI seq = (SequenceI) ( (SequenceNode) node).element();
182
183         if (av.getSequenceColour(seq) == Color.white)
184         {
185           g.setColor(Color.black);
186         }
187         else
188         {
189           g.setColor(av.getSequenceColour(seq).darker());
190         }
191       }
192       else
193       {
194         g.setColor(Color.black);
195       }
196
197       // Draw horizontal line
198       g.drawLine(xstart, ypos, xend, ypos);
199
200       String nodeLabel = "";
201
202       if (showDistances && (node.dist > 0))
203       {
204         nodeLabel = new Format("%-.2f").form(node.dist);
205       }
206
207       if (showBootstrap)
208       {
209         if (showDistances)
210         {
211           nodeLabel = nodeLabel + " : ";
212         }
213
214         nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
215       }
216
217       if (!nodeLabel.equals(""))
218       {
219         g.drawString(nodeLabel, xstart + 2, ypos - 2);
220       }
221
222       String name = (markPlaceholders && node.isPlaceholder())
223           ? (PLACEHOLDER + node.getName()) : node.getName();
224
225       int charWidth = fm.stringWidth(name) + 3;
226       int charHeight = font.getSize();
227
228       Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
229                                      charWidth, charHeight);
230
231       nameHash.put( (SequenceI) node.element(), rect);
232
233       // Colour selected leaves differently
234       SequenceGroup selected = av.getSelectionGroup();
235
236       if ( (selected != null) &&
237           selected.getSequences(null).contains( (SequenceI) node.element()))
238       {
239         g.setColor(Color.gray);
240
241         g.fillRect(xend + 10, ypos - charHeight / 2, charWidth,
242                    charHeight);
243         g.setColor(Color.white);
244       }
245
246       g.drawString(name, xend + 10, ypos + fm.getDescent());
247       g.setColor(Color.black);
248     }
249     else
250     {
251       drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
252                offy);
253       drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
254                offy);
255
256       float height = node.height;
257       float dist = node.dist;
258
259       int xstart = (int) ( (height - dist) * scale) + offx;
260       int xend = (int) (height * scale) + offx;
261       int ypos = (int) (node.ycount * chunk) + offy;
262
263       g.setColor( ( (SequenceNode) node).color.darker());
264
265       // Draw horizontal line
266       g.drawLine(xstart, ypos, xend, ypos);
267       if (node == highlightNode)
268       {
269         g.fillRect(xend - 3, ypos - 3, 6, 6);
270       }
271       else
272       {
273         g.fillRect(xend - 2, ypos - 2, 4, 4);
274       }
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)) // TODO: internal node
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         if (col!=null)
686         {
687           setColor(highlightNode, col);
688         }
689       }
690       else
691       if (evt.getClickCount() > 1)
692       {
693         tree.swapNodes(highlightNode);
694         tree.reCount(tree.getTopNode());
695         tree.findHeight(tree.getTopNode());
696       }
697       else
698       {
699         Vector leaves = new Vector();
700         tree.findLeaves(highlightNode, leaves);
701
702         for (int i = 0; i < leaves.size(); i++)
703         {
704           SequenceI seq =
705               (SequenceI) ( (SequenceNode) leaves.elementAt(i)).element();
706           treeSelectionChanged(seq);
707         }
708       }
709
710       PaintRefresher.Refresh(tp, av.getSequenceSetId());
711       repaint();
712     }
713   }
714
715   public void mouseMoved(MouseEvent evt)
716   {
717     av.setCurrentTree(tree);
718
719     Object ob = findElement(evt.getX(), evt.getY());
720
721     if (ob instanceof SequenceNode)
722     {
723       highlightNode = (SequenceNode) ob;
724       this.setToolTipText(
725           "<html>Left click to select leaves"
726           + "<br>Double-click to invert leaves"
727           + "<br>Right click to change colour");
728       repaint();
729
730     }
731     else
732     {
733       if (highlightNode != null)
734       {
735         highlightNode = null;
736         setToolTipText(null);
737         repaint();
738       }
739     }
740   }
741
742   public void mouseDragged(MouseEvent ect)
743   {}
744
745   /**
746    * DOCUMENT ME!
747    *
748    * @param e DOCUMENT ME!
749    */
750   public void mousePressed(MouseEvent e)
751   {
752     av.setCurrentTree(tree);
753
754     int x = e.getX();
755     int y = e.getY();
756
757     Object ob = findElement(x, y);
758
759     if (ob instanceof SequenceI)
760     {
761       treeSelectionChanged( (Sequence) ob);
762       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
763       repaint();
764       return;
765     }
766     else if (! (ob instanceof SequenceNode))
767     {
768       // Find threshold
769       if (tree.getMaxHeight() != 0)
770       {
771         threshold = (float) (x - offx) / (float) (getWidth() -
772                                                   labelLength - (2 * offx));
773
774         tree.getGroups().removeAllElements();
775         tree.groupNodes(tree.getTopNode(), threshold);
776         setColor(tree.getTopNode(), Color.black);
777
778         AlignmentPanel[] aps = getAssociatedPanels();
779
780         for (int a = 0; a < aps.length; a++)
781         {
782           aps[a].av.setSelectionGroup(null);
783           aps[a].av.alignment.deleteAllGroups();
784           aps[a].av.sequenceColours = null;
785         }
786         colourGroups();
787       }
788
789       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
790       repaint();
791     }
792
793   }
794
795   void colourGroups()
796   {
797     for (int i = 0; i < tree.getGroups().size(); i++)
798     {
799       Color col = new Color( (int) (Math.random() * 255),
800                             (int) (Math.random() * 255),
801                             (int) (Math.random() * 255));
802       setColor( (SequenceNode) tree.getGroups().elementAt(i),
803                col.brighter());
804
805       Vector l = tree.findLeaves( (SequenceNode) tree.getGroups()
806                                  .elementAt(i),
807                                  new Vector());
808
809       Vector sequences = new Vector();
810
811       for (int j = 0; j < l.size(); j++)
812       {
813         SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();
814
815         if (!sequences.contains(s1))
816         {
817           sequences.addElement(s1);
818         }
819       }
820
821       ColourSchemeI cs = null;
822
823       if (av.getGlobalColourScheme() != null)
824       {
825         if (av.getGlobalColourScheme() instanceof UserColourScheme)
826         {
827           cs = new UserColourScheme(
828               ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
829
830         }
831         else
832         {
833           cs = ColourSchemeProperty.getColour(sequences,
834                                               av.alignment.getWidth(),
835                                               ColourSchemeProperty.
836                                               getColourName(
837                                                   av.getGlobalColourScheme()));
838         }
839
840         cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
841                         av.getIgnoreGapsConsensus());
842       }
843
844       SequenceGroup sg = new SequenceGroup(sequences,
845                                            null, cs, true, true, false,
846                                            0,
847                                            av.alignment.getWidth() - 1);
848
849       sg.setName("JTreeGroup:" + sg.hashCode());
850
851       AlignmentPanel[] aps = getAssociatedPanels();
852       for (int a = 0; a < aps.length; a++)
853       {
854         if (aps[a].av.getGlobalColourScheme() != null
855             && aps[a].av.getGlobalColourScheme().conservationApplied())
856         {
857           Conservation c = new Conservation("Group",
858                                             ResidueProperties.propHash, 3,
859                                             sg.getSequences(null),
860                                             sg.getStartRes(), sg.getEndRes());
861
862           c.calculate();
863           c.verdict(false, aps[a].av.ConsPercGaps);
864           sg.cs.setConservation(c);
865         }
866
867         aps[a].av.alignment.addGroup(sg);
868       }
869     }
870
871   }
872
873   /**
874    * DOCUMENT ME!
875    *
876    * @param state DOCUMENT ME!
877    */
878   public void setShowDistances(boolean state)
879   {
880     this.showDistances = state;
881     repaint();
882   }
883
884   /**
885    * DOCUMENT ME!
886    *
887    * @param state DOCUMENT ME!
888    */
889   public void setShowBootstrap(boolean state)
890   {
891     this.showBootstrap = state;
892     repaint();
893   }
894
895   /**
896    * DOCUMENT ME!
897    *
898    * @param state DOCUMENT ME!
899    */
900   public void setMarkPlaceholders(boolean state)
901   {
902     this.markPlaceholders = state;
903     repaint();
904   }
905
906   AlignmentPanel[] getAssociatedPanels()
907   {
908     if (applyToAllViews)
909     {
910       return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
911     }
912     else
913     {
914       return new AlignmentPanel[]
915           {
916           ap};
917     }
918   }
919 }