nullpointer bug fix on consensus thread
[jalview.git] / src / jalview / gui / AlignViewport.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
25 import jalview.analysis.*;
26
27 import jalview.bin.*;
28
29 import jalview.datamodel.*;
30
31 import jalview.schemes.*;
32 import jalview.structure.StructureSelectionManager;
33
34 /**
35  * DOCUMENT ME!
36  *
37  * @author $author$
38  * @version $Revision$
39  */
40 public class AlignViewport
41 {
42     int startRes;
43     int endRes;
44     int startSeq;
45     int endSeq;
46     boolean showJVSuffix = true;
47     boolean showText = true;
48     boolean showColourText = false;
49     boolean showBoxes = true;
50     boolean wrapAlignment = false;
51     boolean renderGaps = true;
52     boolean showSequenceFeatures = false;
53     boolean showAnnotation = true;
54     boolean colourAppliesToAllGroups = true;
55     ColourSchemeI globalColourScheme = null;
56     boolean conservationColourSelected = false;
57     boolean abovePIDThreshold = false;
58     SequenceGroup selectionGroup;
59     int charHeight;
60     int charWidth;
61     boolean validCharWidth;
62     int wrappedWidth;
63     Font font;
64     boolean seqNameItalics;
65     AlignmentI alignment;
66     ColumnSelection colSel = new ColumnSelection();
67     int threshold;
68     int increment;
69     NJTree currentTree = null;
70     boolean scaleAboveWrapped = false;
71     boolean scaleLeftWrapped = true;
72     boolean scaleRightWrapped = true;
73     boolean hasHiddenColumns = false;
74     boolean hasHiddenRows = false;
75     boolean showHiddenMarkers = true;
76
77     boolean cursorMode = false;
78
79     // The following vector holds the features which are
80     // currently visible, in the correct order or rendering
81     Hashtable featuresDisplayed = null;
82
83
84     /** DOCUMENT ME!! */
85     public Hashtable [] hconsensus;
86     AlignmentAnnotation consensus;
87     AlignmentAnnotation conservation;
88     AlignmentAnnotation quality;
89     boolean autoCalculateConsensus = true;
90
91     /** DOCUMENT ME!! */
92     public int ConsPercGaps = 25; // JBPNote : This should be a scalable property!
93
94     // JBPNote Prolly only need this in the applet version.
95   private java.beans.PropertyChangeSupport changeSupport = new java.beans.
96       PropertyChangeSupport(this);
97
98     boolean ignoreGapsInConsensusCalculation = false;
99
100     boolean isDataset = false;
101
102     boolean antiAlias = false;
103
104     boolean padGaps = false;
105
106     Rectangle explodedPosition;
107
108     String viewName;
109
110     String sequenceSetID;
111
112     boolean gatherViewsHere = false;
113
114     Stack historyList = new Stack();
115     Stack redoList = new Stack();
116
117     Hashtable sequenceColours;
118
119     int thresholdTextColour = 0;
120     Color textColour = Color.black;
121     Color textColour2 = Color.white;
122
123     boolean rightAlignIds = false;
124
125     Hashtable hiddenRepSequences;
126
127
128     /**
129      * Creates a new AlignViewport object.
130      *
131      * @param al DOCUMENT ME!
132      */
133     public AlignViewport(AlignmentI al)
134     {
135         setAlignment(al);
136         init();
137     }
138     /**
139      * Create a new AlignViewport with hidden regions
140      * @param al AlignmentI
141      * @param hiddenColumns ColumnSelection
142      */
143   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns)
144   {
145       setAlignment(al);
146     if (hiddenColumns != null)
147     {
148         this.colSel = hiddenColumns;
149         if (hiddenColumns.getHiddenColumns() != null)
150       {
151           hasHiddenColumns = true;
152       }
153     }
154       init();
155     }
156
157     void init()
158     {
159         this.startRes = 0;
160         this.endRes = alignment.getWidth() - 1;
161         this.startSeq = 0;
162         this.endSeq = alignment.getHeight() - 1;
163
164       antiAlias = Cache.getDefault("ANTI_ALIAS", false);
165
166       showJVSuffix = Cache.getDefault("SHOW_JVSUFFIX", true);
167       showAnnotation = Cache.getDefault("SHOW_ANNOTATIONS", true);
168
169       rightAlignIds = Cache.getDefault("RIGHT_ALIGN_IDS", false);
170
171       autoCalculateConsensus = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
172
173       padGaps = Cache.getDefault("PAD_GAPS", true);
174
175        String fontName = Cache.getDefault("FONT_NAME", "SansSerif");
176        String fontStyle = Cache.getDefault("FONT_STYLE", Font.PLAIN + "") ;
177        String fontSize = Cache.getDefault("FONT_SIZE", "10");
178
179        seqNameItalics = Cache.getDefault("ID_ITALICS", true);
180
181        int style = 0;
182
183        if (fontStyle.equals("bold"))
184        {
185          style = 1;
186        }
187        else if (fontStyle.equals("italic"))
188        {
189          style = 2;
190        }
191
192        setFont(new Font(fontName, style, Integer.parseInt(fontSize)));
193
194        alignment.setGapCharacter( Cache.getDefault("GAP_SYMBOL", "-").charAt(0) );
195
196
197         // We must set conservation and consensus before setting colour,
198         // as Blosum and Clustal require this to be done
199         if(hconsensus==null && !isDataset)
200         {
201           if(!alignment.isNucleotide())
202           {
203             conservation = new AlignmentAnnotation("Conservation",
204                 "Conservation of total alignment less than " +
205                 ConsPercGaps + "% gaps",
206                 new Annotation[1], 0f,
207                 11f,
208                 AlignmentAnnotation.BAR_GRAPH);
209             conservation.hasText = true;
210             conservation.autoCalculated=true;
211
212
213             if (Cache.getDefault("SHOW_CONSERVATION", true))
214             {
215               alignment.addAnnotation(conservation);
216             }
217
218             if (Cache.getDefault("SHOW_QUALITY", true))
219             {
220               quality = new AlignmentAnnotation("Quality",
221                                                 "Alignment Quality based on Blosum62 scores",
222                                                 new Annotation[1],
223                                                 0f,
224                                                 11f,
225                                                 AlignmentAnnotation.BAR_GRAPH);
226               quality.hasText = true;
227               quality.autoCalculated=true;
228
229               alignment.addAnnotation(quality);
230             }
231           }
232
233           consensus = new AlignmentAnnotation("Consensus", "PID",
234                                                new Annotation[1], 0f, 100f,
235                                                AlignmentAnnotation.BAR_GRAPH);
236           consensus.hasText = true;
237           consensus.autoCalculated=true;
238
239            if (Cache.getDefault("SHOW_IDENTITY", true))
240            {
241              alignment.addAnnotation(consensus);
242            }
243         }
244
245         if (jalview.bin.Cache.getProperty("DEFAULT_COLOUR") != null)
246         {
247           globalColourScheme = ColourSchemeProperty.getColour(alignment,
248               jalview.bin.Cache.getProperty("DEFAULT_COLOUR"));
249
250             if (globalColourScheme instanceof UserColourScheme)
251             {
252                 globalColourScheme = UserDefinedColours.loadDefaultColours();
253         ( (UserColourScheme) globalColourScheme).setThreshold(0,
254             getIgnoreGapsConsensus());
255             }
256
257             if (globalColourScheme != null)
258             {
259                 globalColourScheme.setConsensus(hconsensus);
260             }
261         }
262
263         wrapAlignment = jalview.bin.Cache.getDefault("WRAP_ALIGNMENT", false);
264     }
265
266
267
268     /**
269      * DOCUMENT ME!
270      *
271      * @param b DOCUMENT ME!
272      */
273     public void setShowSequenceFeatures(boolean b)
274     {
275         showSequenceFeatures = b;
276     }
277
278     public boolean getShowSequenceFeatures()
279     {
280       return showSequenceFeatures;
281     }
282
283   class ConservationThread
284       extends Thread
285     {
286       AlignmentPanel ap;
287       public ConservationThread(AlignmentPanel ap)
288       {
289         this.ap = ap;
290       }
291
292       public void run()
293       {
294         try
295         {
296           updatingConservation = true;
297
298           while (UPDATING_CONSERVATION)
299           {
300             try
301             {
302               if (ap != null)
303               {
304                 ap.paintAlignment(false);
305               }
306               Thread.sleep(200);
307             }
308             catch (Exception ex)
309             {
310               ex.printStackTrace();
311             }
312           }
313
314           UPDATING_CONSERVATION = true;
315
316
317           int alWidth = alignment.getWidth();
318           if(alWidth<0)
319         {
320             return;
321         }
322
323           Conservation cons = new jalview.analysis.Conservation("All",
324               jalview.schemes.ResidueProperties.propHash, 3,
325               alignment.getSequences(), 0, alWidth -1);
326
327           cons.calculate();
328           cons.verdict(false, ConsPercGaps);
329
330           if (quality!=null)
331           {
332             cons.findQuality();
333           }
334
335           char [] sequence = cons.getConsSequence().getSequence();
336           float minR;
337           float minG;
338           float minB;
339           float maxR;
340           float maxG;
341           float maxB;
342           minR = 0.3f;
343           minG = 0.0f;
344           minB = 0f;
345           maxR = 1.0f - minR;
346           maxG = 0.9f - minG;
347           maxB = 0f - minB; // scalable range for colouring both Conservation and Quality
348
349           float min = 0f;
350           float max = 11f;
351           float qmin = 0f;
352           float qmax = 0f;
353
354           char c;
355
356           conservation.annotations = new Annotation[alWidth];
357
358           if (quality!=null)
359           {
360             quality.graphMax = cons.qualityRange[1].floatValue();
361             quality.annotations = new Annotation[alWidth];
362             qmin = cons.qualityRange[0].floatValue();
363             qmax = cons.qualityRange[1].floatValue();
364           }
365
366           for (int i = 0; i < alWidth; i++)
367           {
368             float value = 0;
369
370             c = sequence[i];
371
372             if (Character.isDigit(c))
373           {
374               value = (int) (c - '0');
375           }
376             else if (c == '*')
377           {
378               value = 11;
379           }
380             else if (c == '+')
381           {
382               value = 10;
383           }
384
385             float vprop = value - min;
386             vprop /= max;
387             conservation.annotations[i] =
388                 new Annotation(String.valueOf(c),
389                                String.valueOf(value), ' ', value,
390                                new Color(minR + (maxR * vprop),
391                                          minG + (maxG * vprop),
392                                          minB + (maxB * vprop)));
393
394             // Quality calc
395             if (quality!=null)
396             {
397               value = ( (Double) cons.quality.get(i)).floatValue();
398               vprop = value - qmin;
399               vprop /= qmax;
400             quality.annotations[i] = new Annotation(" ", String.valueOf(value),
401                 ' ',
402                                                value,
403                                                new Color(minR + (maxR * vprop),
404                   minG + (maxG * vprop),
405                   minB + (maxB * vprop)));
406             }
407           }
408         }
409         catch (OutOfMemoryError error)
410         {
411           javax.swing.SwingUtilities.invokeLater(new Runnable()
412           {
413
414
415             public void run()
416             {
417               javax.swing.JOptionPane.showInternalMessageDialog(Desktop.desktop,
418                   "Out of memory calculating conservation!!"
419                   +
420                   "\nSee help files for increasing Java Virtual Machine memory."
421                   , "Out of memory",
422                   javax.swing.JOptionPane.WARNING_MESSAGE);
423             }
424           });
425
426           conservation = null;
427           quality = null;
428
429           System.out.println("Conservation calculation: " + error);
430           System.gc();
431
432         }
433
434         UPDATING_CONSERVATION = false;
435         updatingConservation = false;
436
437         if(ap!=null)
438         {
439         ap.paintAlignment(true);
440         }
441
442       }
443     }
444
445
446     ConservationThread conservationThread;
447
448     ConsensusThread consensusThread;
449
450     boolean consUpdateNeeded = false;
451
452     static boolean UPDATING_CONSENSUS = false;
453
454     static boolean UPDATING_CONSERVATION = false;
455
456     boolean updatingConsensus = false;
457
458     boolean updatingConservation = false;
459
460     /**
461      * DOCUMENT ME!
462      */
463     public void updateConservation(final AlignmentPanel ap)
464     {
465       if (alignment.isNucleotide() || conservation==null)
466     {
467         return;
468     }
469
470       conservationThread = new ConservationThread(ap);
471       conservationThread.start();
472     }
473
474     /**
475      * DOCUMENT ME!
476      */
477     public void updateConsensus(final AlignmentPanel ap)
478     {
479       consensusThread = new ConsensusThread(ap);
480       consensusThread.start();
481     }
482
483   class ConsensusThread
484       extends Thread
485     {
486       AlignmentPanel ap;
487       public ConsensusThread(AlignmentPanel ap)
488       {
489         this.ap = ap;
490       }
491       public void run()
492       {
493         updatingConsensus = true;
494         while (UPDATING_CONSENSUS)
495         {
496           try
497           {
498             if (ap != null)
499             {
500             ap.paintAlignment(false);
501             }
502
503             Thread.sleep(200);
504           }
505           catch (Exception ex)
506           {
507             ex.printStackTrace();
508           }
509         }
510
511
512         UPDATING_CONSENSUS = true;
513
514         try
515         {
516           int aWidth = (alignment!=null) ? alignment.getWidth() : 0; // null pointer possibility here.
517           if(aWidth<0)
518         {
519             return;
520         }
521
522           consensus.annotations = null;
523           consensus.annotations = new Annotation[aWidth];
524
525
526           hconsensus = new Hashtable[aWidth];
527           AAFrequency.calculate(alignment.getSequencesArray(),
528                                 0,
529                                 alignment.getWidth(),
530                                 hconsensus);
531
532           for (int i = 0; i < aWidth; i++)
533           {
534             float value = 0;
535             if (ignoreGapsInConsensusCalculation)
536           {
537               value = ( (Float) hconsensus[i].get(AAFrequency.PID_NOGAPS)).
538                   floatValue();
539           }
540             else
541           {
542               value = ( (Float) hconsensus[i].get(AAFrequency.PID_GAPS)).
543                   floatValue();
544           }
545
546             String maxRes = hconsensus[i].get(AAFrequency.MAXRESIDUE).toString();
547             String mouseOver = hconsensus[i].get(AAFrequency.MAXRESIDUE) + " ";
548
549             if (maxRes.length() > 1)
550             {
551               mouseOver = "[" + maxRes + "] ";
552               maxRes = "+";
553             }
554
555             mouseOver += ( (int) value + "%");
556           consensus.annotations[i] = new Annotation(maxRes, mouseOver, ' ',
557               value);
558           }
559
560
561           if (globalColourScheme != null)
562         {
563             globalColourScheme.setConsensus(hconsensus);
564         }
565
566         }
567         catch (OutOfMemoryError error)
568         {
569           alignment.deleteAnnotation(consensus);
570
571           consensus = null;
572           hconsensus = null;
573           javax.swing.SwingUtilities.invokeLater(new Runnable()
574           {
575             public void run()
576             {
577               javax.swing.JOptionPane.showInternalMessageDialog(Desktop.desktop,
578                   "Out of memory calculating consensus!!"
579                   +
580                   "\nSee help files for increasing Java Virtual Machine memory."
581                   , "Out of memory",
582                   javax.swing.JOptionPane.WARNING_MESSAGE);
583             }
584           });
585
586           System.out.println("Consensus calculation: " + error);
587           System.gc();
588         }
589         UPDATING_CONSENSUS = false;
590         updatingConsensus = false;
591
592         if (ap != null)
593         {
594         ap.paintAlignment(true);
595         }
596       }
597     }
598     /**
599      * get the consensus sequence as displayed under the PID consensus annotation row.
600      * @return consensus sequence as a new sequence object
601      */
602   public SequenceI getConsensusSeq()
603   {
604       if (consensus==null)
605     {
606         updateConsensus(null);
607     }
608       if (consensus==null)
609     {
610         return null;
611     }
612       StringBuffer seqs=new StringBuffer();
613     for (int i = 0; i < consensus.annotations.length; i++)
614     {
615       if (consensus.annotations[i] != null)
616       {
617           if (consensus.annotations[i].description.charAt(0) == '[')
618         {
619             seqs.append(consensus.annotations[i].description.charAt(1));
620         }
621           else
622         {
623             seqs.append(consensus.annotations[i].displayCharacter);
624         }
625       }
626     }
627
628       SequenceI sq = new Sequence("Consensus", seqs.toString());
629     sq.setDescription("Percentage Identity Consensus " +
630                       ( (ignoreGapsInConsensusCalculation) ? " without gaps" :
631                        ""));
632       return sq;
633     }
634     /**
635      * DOCUMENT ME!
636      *
637      * @return DOCUMENT ME!
638      */
639     public SequenceGroup getSelectionGroup()
640     {
641         return selectionGroup;
642     }
643
644     /**
645      * DOCUMENT ME!
646      *
647      * @param sg DOCUMENT ME!
648      */
649     public void setSelectionGroup(SequenceGroup sg)
650     {
651         selectionGroup = sg;
652     }
653
654     /**
655      * DOCUMENT ME!
656      *
657      * @return DOCUMENT ME!
658      */
659     public boolean getConservationSelected()
660     {
661         return conservationColourSelected;
662     }
663
664     /**
665      * DOCUMENT ME!
666      *
667      * @param b DOCUMENT ME!
668      */
669     public void setConservationSelected(boolean b)
670     {
671         conservationColourSelected = b;
672     }
673
674     /**
675      * DOCUMENT ME!
676      *
677      * @return DOCUMENT ME!
678      */
679     public boolean getAbovePIDThreshold()
680     {
681         return abovePIDThreshold;
682     }
683
684     /**
685      * DOCUMENT ME!
686      *
687      * @param b DOCUMENT ME!
688      */
689     public void setAbovePIDThreshold(boolean b)
690     {
691         abovePIDThreshold = b;
692     }
693
694     /**
695      * DOCUMENT ME!
696      *
697      * @return DOCUMENT ME!
698      */
699     public int getStartRes()
700     {
701         return startRes;
702     }
703
704     /**
705      * DOCUMENT ME!
706      *
707      * @return DOCUMENT ME!
708      */
709     public int getEndRes()
710     {
711         return endRes;
712     }
713
714     /**
715      * DOCUMENT ME!
716      *
717      * @return DOCUMENT ME!
718      */
719     public int getStartSeq()
720     {
721         return startSeq;
722     }
723
724     /**
725      * DOCUMENT ME!
726      *
727      * @param cs DOCUMENT ME!
728      */
729     public void setGlobalColourScheme(ColourSchemeI cs)
730     {
731         globalColourScheme = cs;
732     }
733
734     /**
735      * DOCUMENT ME!
736      *
737      * @return DOCUMENT ME!
738      */
739     public ColourSchemeI getGlobalColourScheme()
740     {
741         return globalColourScheme;
742     }
743
744     /**
745      * DOCUMENT ME!
746      *
747      * @param res DOCUMENT ME!
748      */
749     public void setStartRes(int res)
750     {
751         this.startRes = res;
752     }
753
754     /**
755      * DOCUMENT ME!
756      *
757      * @param seq DOCUMENT ME!
758      */
759     public void setStartSeq(int seq)
760     {
761         this.startSeq = seq;
762     }
763
764     /**
765      * DOCUMENT ME!
766      *
767      * @param res DOCUMENT ME!
768      */
769     public void setEndRes(int res)
770     {
771         if (res > (alignment.getWidth() - 1))
772         {
773             // log.System.out.println(" Corrected res from " + res + " to maximum " + (alignment.getWidth()-1));
774             res = alignment.getWidth() - 1;
775         }
776
777         if (res < 0)
778         {
779             res = 0;
780         }
781
782         this.endRes = res;
783     }
784
785     /**
786      * DOCUMENT ME!
787      *
788      * @param seq DOCUMENT ME!
789      */
790     public void setEndSeq(int seq)
791     {
792         if (seq > alignment.getHeight())
793         {
794             seq = alignment.getHeight();
795         }
796
797         if (seq < 0)
798         {
799             seq = 0;
800         }
801
802         this.endSeq = seq;
803     }
804
805     /**
806      * DOCUMENT ME!
807      *
808      * @return DOCUMENT ME!
809      */
810     public int getEndSeq()
811     {
812         return endSeq;
813     }
814
815     /**
816      * DOCUMENT ME!
817      *
818      * @param f DOCUMENT ME!
819      */
820     public void setFont(Font f)
821     {
822         font = f;
823
824         Container c = new Container();
825
826         java.awt.FontMetrics fm = c.getFontMetrics(font);
827         setCharHeight(fm.getHeight());
828         setCharWidth(fm.charWidth('M'));
829         validCharWidth = true;
830     }
831
832     /**
833      * DOCUMENT ME!
834      *
835      * @return DOCUMENT ME!
836      */
837     public Font getFont()
838     {
839         return font;
840     }
841
842     /**
843      * DOCUMENT ME!
844      *
845      * @param w DOCUMENT ME!
846      */
847     public void setCharWidth(int w)
848     {
849         this.charWidth = w;
850     }
851
852     /**
853      * DOCUMENT ME!
854      *
855      * @return DOCUMENT ME!
856      */
857     public int getCharWidth()
858     {
859         return charWidth;
860     }
861
862     /**
863      * DOCUMENT ME!
864      *
865      * @param h DOCUMENT ME!
866      */
867     public void setCharHeight(int h)
868     {
869         this.charHeight = h;
870     }
871
872     /**
873      * DOCUMENT ME!
874      *
875      * @return DOCUMENT ME!
876      */
877     public int getCharHeight()
878     {
879         return charHeight;
880     }
881
882     /**
883      * DOCUMENT ME!
884      *
885      * @param w DOCUMENT ME!
886      */
887     public void setWrappedWidth(int w)
888     {
889         this.wrappedWidth = w;
890     }
891
892     /**
893      * DOCUMENT ME!
894      *
895      * @return DOCUMENT ME!
896      */
897     public int getWrappedWidth()
898     {
899         return wrappedWidth;
900     }
901
902
903     /**
904      * DOCUMENT ME!
905      *
906      * @return DOCUMENT ME!
907      */
908     public AlignmentI getAlignment()
909     {
910         return alignment;
911     }
912
913     /**
914      * DOCUMENT ME!
915      *
916      * @param align DOCUMENT ME!
917      */
918     public void setAlignment(AlignmentI align)
919     {
920       if (alignment!=null && alignment.getCodonFrames()!=null)
921       {
922         StructureSelectionManager.getStructureSelectionManager().removeMappings(alignment.getCodonFrames());
923       }
924         this.alignment = align;
925         if (alignment.getCodonFrames()!=null)
926         {
927           StructureSelectionManager.getStructureSelectionManager().addMappings(alignment.getCodonFrames());
928         }
929     }
930
931     /**
932      * DOCUMENT ME!
933      *
934      * @param state DOCUMENT ME!
935      */
936     public void setWrapAlignment(boolean state)
937     {
938         wrapAlignment = state;
939     }
940
941     /**
942      * DOCUMENT ME!
943      *
944      * @param state DOCUMENT ME!
945      */
946     public void setShowText(boolean state)
947     {
948         showText = state;
949     }
950
951     /**
952      * DOCUMENT ME!
953      *
954      * @param state DOCUMENT ME!
955      */
956     public void setRenderGaps(boolean state)
957     {
958         renderGaps = state;
959     }
960
961     /**
962      * DOCUMENT ME!
963      *
964      * @return DOCUMENT ME!
965      */
966     public boolean getColourText()
967     {
968         return showColourText;
969     }
970
971     /**
972      * DOCUMENT ME!
973      *
974      * @param state DOCUMENT ME!
975      */
976     public void setColourText(boolean state)
977     {
978         showColourText = state;
979     }
980
981     /**
982      * DOCUMENT ME!
983      *
984      * @param state DOCUMENT ME!
985      */
986     public void setShowBoxes(boolean state)
987     {
988         showBoxes = state;
989     }
990
991     /**
992      * DOCUMENT ME!
993      *
994      * @return DOCUMENT ME!
995      */
996     public boolean getWrapAlignment()
997     {
998         return wrapAlignment;
999     }
1000
1001     /**
1002      * DOCUMENT ME!
1003      *
1004      * @return DOCUMENT ME!
1005      */
1006     public boolean getShowText()
1007     {
1008         return showText;
1009     }
1010
1011     /**
1012      * DOCUMENT ME!
1013      *
1014      * @return DOCUMENT ME!
1015      */
1016     public boolean getShowBoxes()
1017     {
1018         return showBoxes;
1019     }
1020
1021     /**
1022      * DOCUMENT ME!
1023      *
1024      * @return DOCUMENT ME!
1025      */
1026     public char getGapCharacter()
1027     {
1028         return getAlignment().getGapCharacter();
1029     }
1030
1031     /**
1032      * DOCUMENT ME!
1033      *
1034      * @param gap DOCUMENT ME!
1035      */
1036     public void setGapCharacter(char gap)
1037     {
1038         if (getAlignment() != null)
1039         {
1040             getAlignment().setGapCharacter(gap);
1041         }
1042     }
1043
1044     /**
1045      * DOCUMENT ME!
1046      *
1047      * @param thresh DOCUMENT ME!
1048      */
1049     public void setThreshold(int thresh)
1050     {
1051         threshold = thresh;
1052     }
1053
1054     /**
1055      * DOCUMENT ME!
1056      *
1057      * @return DOCUMENT ME!
1058      */
1059     public int getThreshold()
1060     {
1061         return threshold;
1062     }
1063
1064     /**
1065      * DOCUMENT ME!
1066      *
1067      * @param inc DOCUMENT ME!
1068      */
1069     public void setIncrement(int inc)
1070     {
1071         increment = inc;
1072     }
1073
1074     /**
1075      * DOCUMENT ME!
1076      *
1077      * @return DOCUMENT ME!
1078      */
1079     public int getIncrement()
1080     {
1081         return increment;
1082     }
1083
1084
1085     /**
1086      * DOCUMENT ME!
1087      *
1088      * @return DOCUMENT ME!
1089      */
1090     public ColumnSelection getColumnSelection()
1091     {
1092         return colSel;
1093     }
1094
1095
1096     /**
1097      * DOCUMENT ME!
1098      *
1099      * @param tree DOCUMENT ME!
1100      */
1101     public void setCurrentTree(NJTree tree)
1102     {
1103         currentTree = tree;
1104     }
1105
1106     /**
1107      * DOCUMENT ME!
1108      *
1109      * @return DOCUMENT ME!
1110      */
1111     public NJTree getCurrentTree()
1112     {
1113         return currentTree;
1114     }
1115
1116     /**
1117      * DOCUMENT ME!
1118      *
1119      * @param b DOCUMENT ME!
1120      */
1121     public void setColourAppliesToAllGroups(boolean b)
1122     {
1123         colourAppliesToAllGroups = b;
1124     }
1125
1126     /**
1127      * DOCUMENT ME!
1128      *
1129      * @return DOCUMENT ME!
1130      */
1131     public boolean getColourAppliesToAllGroups()
1132     {
1133         return colourAppliesToAllGroups;
1134     }
1135
1136     /**
1137      * DOCUMENT ME!
1138      *
1139      * @return DOCUMENT ME!
1140      */
1141     public boolean getShowJVSuffix()
1142     {
1143         return showJVSuffix;
1144     }
1145
1146     /**
1147      * DOCUMENT ME!
1148      *
1149      * @param b DOCUMENT ME!
1150      */
1151     public void setShowJVSuffix(boolean b)
1152     {
1153         showJVSuffix = b;
1154     }
1155
1156
1157     /**
1158      * DOCUMENT ME!
1159      *
1160      * @return DOCUMENT ME!
1161      */
1162     public boolean getShowAnnotation()
1163     {
1164         return showAnnotation;
1165     }
1166
1167     /**
1168      * DOCUMENT ME!
1169      *
1170      * @param b DOCUMENT ME!
1171      */
1172     public void setShowAnnotation(boolean b)
1173     {
1174         showAnnotation = b;
1175     }
1176
1177     /**
1178      * DOCUMENT ME!
1179      *
1180      * @return DOCUMENT ME!
1181      */
1182     public boolean getScaleAboveWrapped()
1183     {
1184         return scaleAboveWrapped;
1185     }
1186
1187     /**
1188      * DOCUMENT ME!
1189      *
1190      * @return DOCUMENT ME!
1191      */
1192     public boolean getScaleLeftWrapped()
1193     {
1194         return scaleLeftWrapped;
1195     }
1196
1197     /**
1198      * DOCUMENT ME!
1199      *
1200      * @return DOCUMENT ME!
1201      */
1202     public boolean getScaleRightWrapped()
1203     {
1204         return scaleRightWrapped;
1205     }
1206
1207     /**
1208      * DOCUMENT ME!
1209      *
1210      * @param b DOCUMENT ME!
1211      */
1212     public void setScaleAboveWrapped(boolean b)
1213     {
1214         scaleAboveWrapped = b;
1215     }
1216
1217     /**
1218      * DOCUMENT ME!
1219      *
1220      * @param b DOCUMENT ME!
1221      */
1222     public void setScaleLeftWrapped(boolean b)
1223     {
1224         scaleLeftWrapped = b;
1225     }
1226
1227     /**
1228      * DOCUMENT ME!
1229      *
1230      * @param b DOCUMENT ME!
1231      */
1232     public void setScaleRightWrapped(boolean b)
1233     {
1234         scaleRightWrapped = b;
1235     }
1236
1237     /**
1238      * Property change listener for changes in alignment
1239      *
1240      * @param listener DOCUMENT ME!
1241      */
1242     public void addPropertyChangeListener(
1243         java.beans.PropertyChangeListener listener)
1244     {
1245         changeSupport.addPropertyChangeListener(listener);
1246     }
1247
1248     /**
1249      * DOCUMENT ME!
1250      *
1251      * @param listener DOCUMENT ME!
1252      */
1253     public void removePropertyChangeListener(
1254         java.beans.PropertyChangeListener listener)
1255     {
1256         changeSupport.removePropertyChangeListener(listener);
1257     }
1258
1259     /**
1260      * Property change listener for changes in alignment
1261      *
1262      * @param prop DOCUMENT ME!
1263      * @param oldvalue DOCUMENT ME!
1264      * @param newvalue DOCUMENT ME!
1265      */
1266     public void firePropertyChange(String prop, Object oldvalue, Object newvalue)
1267     {
1268         changeSupport.firePropertyChange(prop, oldvalue, newvalue);
1269     }
1270
1271     public void setIgnoreGapsConsensus(boolean b, AlignmentPanel ap)
1272     {
1273       ignoreGapsInConsensusCalculation = b;
1274       updateConsensus(ap);
1275       if(globalColourScheme!=null)
1276       {
1277       globalColourScheme.setThreshold(globalColourScheme.getThreshold(),
1278                                       ignoreGapsInConsensusCalculation);
1279       }
1280     }
1281
1282     public boolean getIgnoreGapsConsensus()
1283     {
1284      return ignoreGapsInConsensusCalculation;
1285     }
1286
1287     public void setDataset(boolean b)
1288     {
1289       isDataset = b;
1290     }
1291
1292     public boolean isDataset()
1293     {
1294       return isDataset;
1295     }
1296
1297
1298     public void hideSelectedColumns()
1299     {
1300       if (colSel.size() < 1)
1301     {
1302         return;
1303     }
1304
1305       colSel.hideSelectedColumns();
1306       setSelectionGroup(null);
1307
1308       hasHiddenColumns = true;
1309     }
1310
1311
1312     public void hideColumns(int start, int end)
1313     {
1314       if(start==end)
1315     {
1316         colSel.hideColumns(start);
1317     }
1318       else
1319     {
1320         colSel.hideColumns(start, end);
1321     }
1322
1323       hasHiddenColumns = true;
1324     }
1325
1326     public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
1327     {
1328       int sSize = sg.getSize();
1329       if(sSize < 2)
1330     {
1331         return;
1332     }
1333
1334       if(hiddenRepSequences==null)
1335     {
1336         hiddenRepSequences = new Hashtable();
1337     }
1338
1339        hiddenRepSequences.put(repSequence, sg);
1340
1341       //Hide all sequences except the repSequence
1342       SequenceI [] seqs = new SequenceI[sSize-1];
1343       int index = 0;
1344       for(int i=0; i<sSize; i++)
1345     {
1346         if(sg.getSequenceAt(i)!=repSequence)
1347         {
1348           if(index==sSize-1)
1349         {
1350             return;
1351         }
1352
1353           seqs[index++] = sg.getSequenceAt(i);
1354         }
1355     }
1356
1357       hideSequence(seqs);
1358
1359     }
1360
1361     public void hideAllSelectedSeqs()
1362     {
1363     if (selectionGroup == null || selectionGroup.getSize()<1)
1364     {
1365         return;
1366     }
1367
1368       SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
1369
1370       hideSequence(seqs);
1371
1372       setSelectionGroup(null);
1373     }
1374
1375     public void hideSequence(SequenceI [] seq)
1376     {
1377       if(seq!=null)
1378       {
1379         for (int i = 0; i < seq.length; i++)
1380         {
1381           alignment.getHiddenSequences().hideSequence(seq[i]);
1382         }
1383         hasHiddenRows = true;
1384         firePropertyChange("alignment", null, alignment.getSequences());
1385       }
1386     }
1387
1388     public void showSequence(int index)
1389     {
1390       Vector tmp = alignment.getHiddenSequences().showSequence(index
1391           , hiddenRepSequences);
1392       if(tmp.size()>0)
1393       {
1394         if(selectionGroup==null)
1395         {
1396           selectionGroup = new SequenceGroup();
1397           selectionGroup.setEndRes(alignment.getWidth()-1);
1398         }
1399
1400         for (int t = 0; t < tmp.size(); t++)
1401         {
1402           selectionGroup.addSequence(
1403               (SequenceI) tmp.elementAt(t), false
1404               );
1405         }
1406         firePropertyChange("alignment", null, alignment.getSequences());
1407       }
1408
1409       if(alignment.getHiddenSequences().getSize()<1)
1410     {
1411         hasHiddenRows = false;
1412     }
1413   }
1414
1415     public void showColumn(int col)
1416     {
1417       colSel.revealHiddenColumns(col);
1418       if(colSel.getHiddenColumns()==null)
1419     {
1420         hasHiddenColumns = false;
1421     }
1422   }
1423
1424     public void showAllHiddenColumns()
1425     {
1426       colSel.revealAllHiddenColumns();
1427       hasHiddenColumns = false;
1428     }
1429
1430     public void showAllHiddenSeqs()
1431     {
1432       if(alignment.getHiddenSequences().getSize()>0)
1433       {
1434         if(selectionGroup==null)
1435         {
1436           selectionGroup = new SequenceGroup();
1437           selectionGroup.setEndRes(alignment.getWidth()-1);
1438         }
1439         Vector tmp = alignment.getHiddenSequences().showAll(hiddenRepSequences);
1440         for(int t=0; t<tmp.size(); t++)
1441         {
1442           selectionGroup.addSequence(
1443               (SequenceI)tmp.elementAt(t), false
1444               );
1445         }
1446         firePropertyChange("alignment", null, alignment.getSequences());
1447         hasHiddenRows = false;
1448         hiddenRepSequences = null;
1449       }
1450     }
1451
1452
1453
1454     public void invertColumnSelection()
1455     {
1456       colSel.invertColumnSelection(0,alignment.getWidth());
1457     }
1458
1459     public int adjustForHiddenSeqs(int alignmentIndex)
1460     {
1461       return alignment.getHiddenSequences().adjustForHiddenSeqs(alignmentIndex);
1462     }
1463
1464     /**
1465      * This method returns an array of new SequenceI objects
1466      * derived from the whole alignment or just the current
1467      * selection with start and end points adjusted
1468      * @note if you need references to the actual SequenceI objects in the alignment or currently selected then use getSequenceSelection()
1469      * @return selection as new sequenceI objects
1470      */
1471     public SequenceI[] getSelectionAsNewSequence()
1472     {
1473       SequenceI[] sequences;
1474
1475       if (selectionGroup == null)
1476     {
1477         sequences = alignment.getSequencesArray();
1478         AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation();
1479         for (int i=0; i<sequences.length; i++)
1480         {
1481           sequences[i] = new Sequence(sequences[i], annots); // construct new sequence with subset of visible annotation
1482         }
1483     }
1484       else
1485     {
1486         sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1487     }
1488
1489       return sequences;
1490     }
1491     
1492     /**
1493      * get the currently selected sequence objects or all the sequences in the alignment.
1494      * @return array of references to sequence objects
1495      */
1496     public SequenceI[] getSequenceSelection()
1497     {
1498       SequenceI[] sequences;
1499       if (selectionGroup==null)
1500       {
1501         sequences = alignment.getSequencesArray();
1502       }
1503       else
1504       {
1505         sequences = selectionGroup.getSequencesInOrder(alignment);
1506       }
1507       return sequences;
1508     }
1509     /**
1510      * This method returns the visible alignment as text, as
1511      * seen on the GUI, ie if columns are hidden they will not
1512      * be returned in the result.
1513      * Use this for calculating trees, PCA, redundancy etc on views
1514      * which contain hidden columns.
1515      * @return String[]
1516      */
1517   public jalview.datamodel.CigarArray getViewAsCigars(boolean
1518       selectedRegionOnly)
1519     {
1520       CigarArray selection=null;
1521       SequenceI [] seqs= null;
1522       int i, iSize;
1523       int start = 0, end = 0;
1524       if(selectedRegionOnly && selectionGroup!=null)
1525       {
1526         iSize = selectionGroup.getSize();
1527         seqs = selectionGroup.getSequencesInOrder(alignment);
1528         start = selectionGroup.getStartRes();
1529         end = selectionGroup.getEndRes(); // inclusive for start and end in SeqCigar constructor
1530       }
1531       else
1532       {
1533         iSize = alignment.getHeight();
1534         seqs = alignment.getSequencesArray();
1535         end = alignment.getWidth()-1;
1536       }
1537       SeqCigar[] selseqs = new SeqCigar[iSize];
1538       for(i=0; i<iSize; i++)
1539       {
1540         selseqs[i] = new SeqCigar(seqs[i], start, end);
1541       }
1542       selection=new CigarArray(selseqs);
1543       // now construct the CigarArray operations
1544     if (hasHiddenColumns)
1545     {
1546         Vector regions = colSel.getHiddenColumns();
1547         int [] region;
1548         int hideStart, hideEnd;
1549         int last=start;
1550         for (int j = 0; last<end & j < regions.size(); j++)
1551         {
1552           region = (int[]) regions.elementAt(j);
1553           hideStart = region[0];
1554           hideEnd = region[1];
1555           // edit hidden regions to selection range
1556         if (hideStart < last)
1557         {
1558             if (hideEnd > last)
1559             {
1560               hideStart = last;
1561           }
1562           else
1563           {
1564               continue;
1565           }
1566         }
1567
1568           if (hideStart>end)
1569         {
1570             break;
1571         }
1572
1573           if (hideEnd>end)
1574         {
1575             hideEnd=end;
1576         }
1577
1578           if (hideStart>hideEnd)
1579         {
1580             break;
1581         }
1582           /**
1583            * form operations...
1584            */
1585           if (last<hideStart)
1586         {
1587             selection.addOperation(CigarArray.M, hideStart-last);
1588         }
1589           selection.addOperation(CigarArray.D, 1+hideEnd-hideStart);
1590           last = hideEnd+1;
1591         }
1592         // Final match if necessary.
1593         if (last<end)
1594       {
1595           selection.addOperation(CigarArray.M, end-last+1);
1596       }
1597     }
1598     else
1599     {
1600         selection.addOperation(CigarArray.M, end-start+1);
1601       }
1602       return selection;
1603     }
1604     /**
1605      * return a compact representation of the current alignment selection to
1606      * pass to an analysis function
1607      * @param selectedOnly boolean true to just return the selected view
1608      * @return AlignmentView
1609      */
1610   jalview.datamodel.AlignmentView getAlignmentView(boolean selectedOnly)
1611   {
1612       // JBPNote:
1613       // this is here because the AlignmentView constructor modifies the CigarArray
1614       // object. Refactoring of Cigar and alignment view representation should
1615       // be done to remove redundancy.
1616       CigarArray aligview = getViewAsCigars(selectedOnly);
1617     if (aligview != null)
1618     {
1619         return new AlignmentView(aligview,
1620                                (selectedOnly && selectionGroup != null) ?
1621                                selectionGroup.getStartRes() : 0);
1622       }
1623       return null;
1624     }
1625     /**
1626      * This method returns the visible alignment as text, as
1627      * seen on the GUI, ie if columns are hidden they will not
1628      * be returned in the result.
1629      * Use this for calculating trees, PCA, redundancy etc on views
1630      * which contain hidden columns.
1631      * @return String[]
1632      */
1633     public String [] getViewAsString(boolean selectedRegionOnly)
1634     {
1635       String [] selection = null;
1636       SequenceI [] seqs= null;
1637       int i, iSize;
1638       int start = 0, end = 0;
1639       if(selectedRegionOnly && selectionGroup!=null)
1640       {
1641         iSize = selectionGroup.getSize();
1642         seqs = selectionGroup.getSequencesInOrder(alignment);
1643         start = selectionGroup.getStartRes();
1644         end = selectionGroup.getEndRes()+1;
1645       }
1646       else
1647       {
1648         iSize = alignment.getHeight();
1649         seqs = alignment.getSequencesArray();
1650         end = alignment.getWidth();
1651       }
1652
1653       selection = new String[iSize];
1654     if (hasHiddenColumns)
1655     {
1656         selection = colSel.getVisibleSequenceStrings(start, end, seqs);
1657     }
1658     else
1659     {
1660         for(i=0; i<iSize; i++)
1661         {
1662           selection[i] = seqs[i].getSequenceAsString(start, end);
1663         }
1664
1665       }
1666       return selection;
1667     }
1668
1669   public int [][] getVisibleRegionBoundaries(int min, int max)
1670   {
1671     Vector regions = new Vector();
1672     int start = min;
1673     int end = max;
1674
1675     do
1676     {
1677       if (hasHiddenColumns)
1678       {
1679         if (start == 0)
1680         {
1681           start = colSel.adjustForHiddenColumns(start);
1682         }
1683
1684         end = colSel.getHiddenBoundaryRight(start);
1685         if (start == end)
1686         {
1687           end = max;
1688         }
1689         if (end > max)
1690         {
1691           end = max;
1692         }
1693       }
1694
1695       regions.addElement(new int[]
1696                          {start, end});
1697
1698       if (hasHiddenColumns)
1699       {
1700         start = colSel.adjustForHiddenColumns(end);
1701         start = colSel.getHiddenBoundaryLeft(start) + 1;
1702       }
1703     }
1704     while (end < max);
1705
1706     int[][] startEnd = new int[regions.size()][2];
1707
1708     regions.copyInto(startEnd);
1709
1710     return startEnd;
1711
1712   }
1713
1714     public boolean getShowHiddenMarkers()
1715     {
1716       return showHiddenMarkers;
1717     }
1718
1719     public void setShowHiddenMarkers(boolean show)
1720     {
1721       showHiddenMarkers = show;
1722     }
1723
1724     public String getSequenceSetId()
1725     {
1726       if(sequenceSetID==null)
1727     {
1728         sequenceSetID =  alignment.hashCode()+"";
1729     }
1730
1731       return sequenceSetID;
1732     }
1733
1734     public void alignmentChanged(AlignmentPanel ap)
1735     {
1736         if (padGaps)
1737     {
1738           alignment.padGaps();
1739     }
1740
1741         if (hconsensus != null && autoCalculateConsensus)
1742         {
1743           updateConsensus(ap);
1744           updateConservation(ap);
1745         }
1746
1747         //Reset endRes of groups if beyond alignment width
1748         int alWidth = alignment.getWidth();
1749         Vector groups = alignment.getGroups();
1750         if(groups!=null)
1751         {
1752           for(int i=0; i<groups.size(); i++)
1753           {
1754             SequenceGroup sg = (SequenceGroup)groups.elementAt(i);
1755             if(sg.getEndRes()>alWidth)
1756         {
1757               sg.setEndRes(alWidth-1);
1758           }
1759         }
1760     }
1761
1762         if(selectionGroup!=null && selectionGroup.getEndRes()>alWidth)
1763     {
1764           selectionGroup.setEndRes(alWidth-1);
1765     }
1766
1767         resetAllColourSchemes();
1768
1769        // alignment.adjustSequenceAnnotations();
1770     }
1771
1772
1773     void resetAllColourSchemes()
1774     {
1775       ColourSchemeI cs = globalColourScheme;
1776       if(cs!=null)
1777       {
1778         if (cs instanceof ClustalxColourScheme)
1779         {
1780           ( (ClustalxColourScheme) cs).
1781               resetClustalX(alignment.getSequences(),
1782                             alignment.getWidth());
1783         }
1784
1785         cs.setConsensus(hconsensus);
1786         if (cs.conservationApplied())
1787         {
1788           Alignment al = (Alignment) alignment;
1789           Conservation c = new Conservation("All",
1790                                             ResidueProperties.propHash, 3,
1791                                             al.getSequences(), 0,
1792                                             al.getWidth() - 1);
1793           c.calculate();
1794           c.verdict(false, ConsPercGaps);
1795
1796           cs.setConservation(c);
1797         }
1798       }
1799
1800       int s, sSize = alignment.getGroups().size();
1801       for(s=0; s<sSize; s++)
1802       {
1803         SequenceGroup sg = (SequenceGroup)alignment.getGroups().elementAt(s);
1804         if(sg.cs!=null && sg.cs instanceof ClustalxColourScheme)
1805         {
1806           ((ClustalxColourScheme)sg.cs).resetClustalX(
1807               sg.getSequences(hiddenRepSequences), sg.getWidth());
1808         }
1809         sg.recalcConservation();
1810       }
1811     }
1812
1813
1814     public Color getSequenceColour(SequenceI seq)
1815     {
1816       if(sequenceColours==null || !sequenceColours.containsKey(seq))
1817     {
1818         return Color.white;
1819     }
1820       else
1821     {
1822         return (Color)sequenceColours.get(seq);
1823     }
1824   }
1825
1826     public void setSequenceColour(SequenceI seq, Color col)
1827     {
1828       if(sequenceColours==null)
1829     {
1830         sequenceColours = new Hashtable();
1831     }
1832
1833       if(col == null)
1834     {
1835         sequenceColours.remove(seq);
1836     }
1837       else
1838     {
1839         sequenceColours.put(seq, col);
1840     }
1841     }
1842     /**
1843      * returns the visible column regions of the alignment
1844      * @param selectedRegionOnly true to just return the contigs intersecting with the selected area
1845      * @return
1846      */
1847     public int[] getViewAsVisibleContigs(boolean selectedRegionOnly) {
1848       int[] viscontigs=null;
1849       int start = 0, end = 0;
1850       if(selectedRegionOnly && selectionGroup!=null)
1851       {
1852         start = selectionGroup.getStartRes();
1853         end = selectionGroup.getEndRes()+1;
1854       }
1855       else
1856       {
1857         end = alignment.getWidth();
1858       }
1859       viscontigs = colSel.getVisibleContigs(start, end);
1860       return viscontigs;
1861     }
1862     /**
1863      * get hash of undo and redo list for the alignment
1864      * @return long[] { historyList.hashCode, redoList.hashCode };
1865      */
1866     public long[] getUndoRedoHash()
1867     {
1868       if (historyList==null || redoList==null)
1869         return new long[] { -1, -1};
1870       return new long[] { historyList.hashCode(), this.redoList.hashCode() };
1871     }
1872     /**
1873      * test if a particular set of hashcodes are different to the hashcodes for the undo and redo list.
1874      * @param undoredo the stored set of hashcodes as returned by getUndoRedoHash
1875      * @return true if the hashcodes differ (ie the alignment has been edited) or the stored hashcode array differs in size
1876      */
1877     public boolean isUndoRedoHashModified(long[] undoredo)
1878     {
1879       if (undoredo==null)
1880       {
1881         return true;
1882       }
1883       long[] cstate = getUndoRedoHash();
1884       if (cstate.length!=undoredo.length)
1885       {  return true; }
1886       
1887       for (int i=0; i<cstate.length; i++)
1888       {
1889         if (cstate[i]!=undoredo[i])
1890         {
1891           return true;
1892         }
1893       }
1894       return false;
1895     }
1896 }