Merge branch 'develop' into features/JAL-2446NCList
[jalview.git] / src / jalview / appletgui / SeqPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.appletgui;
22
23 import jalview.api.AlignViewportI;
24 import jalview.commands.EditCommand;
25 import jalview.commands.EditCommand.Action;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.ColumnSelection;
28 import jalview.datamodel.HiddenColumns;
29 import jalview.datamodel.SearchResultMatchI;
30 import jalview.datamodel.SearchResults;
31 import jalview.datamodel.SearchResultsI;
32 import jalview.datamodel.Sequence;
33 import jalview.datamodel.SequenceFeature;
34 import jalview.datamodel.SequenceGroup;
35 import jalview.datamodel.SequenceI;
36 import jalview.schemes.ResidueProperties;
37 import jalview.structure.SelectionListener;
38 import jalview.structure.SelectionSource;
39 import jalview.structure.SequenceListener;
40 import jalview.structure.StructureSelectionManager;
41 import jalview.structure.VamsasSource;
42 import jalview.util.Comparison;
43 import jalview.util.MappingUtils;
44 import jalview.util.MessageManager;
45 import jalview.viewmodel.AlignmentViewport;
46 import jalview.viewmodel.ViewportRanges;
47
48 import java.awt.BorderLayout;
49 import java.awt.Font;
50 import java.awt.FontMetrics;
51 import java.awt.Panel;
52 import java.awt.Point;
53 import java.awt.event.InputEvent;
54 import java.awt.event.MouseEvent;
55 import java.awt.event.MouseListener;
56 import java.awt.event.MouseMotionListener;
57 import java.util.List;
58 import java.util.Vector;
59
60 public class SeqPanel extends Panel implements MouseMotionListener,
61         MouseListener, SequenceListener, SelectionListener
62 {
63
64   public SeqCanvas seqCanvas;
65
66   public AlignmentPanel ap;
67
68   protected int lastres;
69
70   protected int startseq;
71
72   protected AlignViewport av;
73
74   // if character is inserted or deleted, we will need to recalculate the
75   // conservation
76   boolean seqEditOccurred = false;
77
78   ScrollThread scrollThread = null;
79
80   boolean mouseDragging = false;
81
82   boolean editingSeqs = false;
83
84   boolean groupEditing = false;
85
86   int oldSeq = -1;
87
88   boolean changeEndSeq = false;
89
90   boolean changeStartSeq = false;
91
92   boolean changeEndRes = false;
93
94   boolean changeStartRes = false;
95
96   SequenceGroup stretchGroup = null;
97
98   StringBuffer keyboardNo1;
99
100   StringBuffer keyboardNo2;
101
102   boolean mouseWheelPressed = false;
103
104   Point lastMousePress;
105
106   EditCommand editCommand;
107
108   StructureSelectionManager ssm;
109
110   public SeqPanel(AlignViewport avp, AlignmentPanel p)
111   {
112     this.av = avp;
113
114     seqCanvas = new SeqCanvas(avp);
115     setLayout(new BorderLayout());
116     add(seqCanvas);
117
118     ap = p;
119
120     seqCanvas.addMouseMotionListener(this);
121     seqCanvas.addMouseListener(this);
122     ssm = StructureSelectionManager.getStructureSelectionManager(av.applet);
123     ssm.addStructureViewerListener(this);
124     ssm.addSelectionListener(this);
125
126     seqCanvas.repaint();
127   }
128
129   void endEditing()
130   {
131     if (editCommand != null && editCommand.getSize() > 0)
132     {
133       ap.alignFrame.addHistoryItem(editCommand);
134       av.firePropertyChange("alignment", null, av.getAlignment()
135               .getSequences());
136     }
137
138     startseq = -1;
139     lastres = -1;
140     editingSeqs = false;
141     groupEditing = false;
142     keyboardNo1 = null;
143     keyboardNo2 = null;
144     editCommand = null;
145   }
146
147   void setCursorRow()
148   {
149     seqCanvas.cursorY = getKeyboardNo1() - 1;
150     scrollToVisible();
151   }
152
153   void setCursorColumn()
154   {
155     seqCanvas.cursorX = getKeyboardNo1() - 1;
156     scrollToVisible();
157   }
158
159   void setCursorRowAndColumn()
160   {
161     if (keyboardNo2 == null)
162     {
163       keyboardNo2 = new StringBuffer();
164     }
165     else
166     {
167       seqCanvas.cursorX = getKeyboardNo1() - 1;
168       seqCanvas.cursorY = getKeyboardNo2() - 1;
169       scrollToVisible();
170     }
171   }
172
173   void setCursorPosition()
174   {
175     SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
176
177     seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
178     scrollToVisible();
179   }
180
181   void moveCursor(int dx, int dy)
182   {
183     seqCanvas.cursorX += dx;
184     seqCanvas.cursorY += dy;
185     if (av.hasHiddenColumns()
186             && !av.getAlignment().getHiddenColumns()
187                     .isVisible(seqCanvas.cursorX))
188     {
189       int original = seqCanvas.cursorX - dx;
190       int maxWidth = av.getAlignment().getWidth();
191
192       while (!av.getAlignment().getHiddenColumns()
193               .isVisible(seqCanvas.cursorX)
194               && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
195       {
196         seqCanvas.cursorX += dx;
197       }
198
199       if (seqCanvas.cursorX >= maxWidth
200               || !av.getAlignment().getHiddenColumns()
201                       .isVisible(seqCanvas.cursorX))
202       {
203         seqCanvas.cursorX = original;
204       }
205     }
206     scrollToVisible();
207   }
208
209   void scrollToVisible()
210   {
211     if (seqCanvas.cursorX < 0)
212     {
213       seqCanvas.cursorX = 0;
214     }
215     else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
216     {
217       seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
218     }
219
220     if (seqCanvas.cursorY < 0)
221     {
222       seqCanvas.cursorY = 0;
223     }
224     else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
225     {
226       seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
227     }
228
229     endEditing();
230     if (av.getWrapAlignment())
231     {
232       av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX);
233     }
234     else
235     {
236       ViewportRanges ranges = av.getRanges();
237       HiddenColumns hidden = av.getAlignment().getHiddenColumns();
238       while (seqCanvas.cursorY < ranges.getStartSeq())
239       {
240         ranges.scrollUp(true);
241       }
242       while (seqCanvas.cursorY > ranges.getEndSeq())
243       {
244         ranges.scrollUp(false);
245       }
246       while (seqCanvas.cursorX < hidden.adjustForHiddenColumns(ranges
247               .getStartRes()))
248       {
249
250         if (!ranges.scrollRight(false))
251         {
252           break;
253         }
254       }
255       while (seqCanvas.cursorX > hidden.adjustForHiddenColumns(ranges
256               .getEndRes()))
257       {
258         if (!ranges.scrollRight(true))
259         {
260           break;
261         }
262       }
263     }
264     setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
265             seqCanvas.cursorX, seqCanvas.cursorY);
266
267     seqCanvas.repaint();
268   }
269
270   void setSelectionAreaAtCursor(boolean topLeft)
271   {
272     SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
273
274     if (av.getSelectionGroup() != null)
275     {
276       SequenceGroup sg = av.getSelectionGroup();
277       // Find the top and bottom of this group
278       int min = av.getAlignment().getHeight(), max = 0;
279       for (int i = 0; i < sg.getSize(); i++)
280       {
281         int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
282         if (index > max)
283         {
284           max = index;
285         }
286         if (index < min)
287         {
288           min = index;
289         }
290       }
291
292       max++;
293
294       if (topLeft)
295       {
296         sg.setStartRes(seqCanvas.cursorX);
297         if (sg.getEndRes() < seqCanvas.cursorX)
298         {
299           sg.setEndRes(seqCanvas.cursorX);
300         }
301
302         min = seqCanvas.cursorY;
303       }
304       else
305       {
306         sg.setEndRes(seqCanvas.cursorX);
307         if (sg.getStartRes() > seqCanvas.cursorX)
308         {
309           sg.setStartRes(seqCanvas.cursorX);
310         }
311
312         max = seqCanvas.cursorY + 1;
313       }
314
315       if (min > max)
316       {
317         // Only the user can do this
318         av.setSelectionGroup(null);
319       }
320       else
321       {
322         // Now add any sequences between min and max
323         sg.clear();
324         for (int i = min; i < max; i++)
325         {
326           sg.addSequence(av.getAlignment().getSequenceAt(i), false);
327         }
328       }
329     }
330
331     if (av.getSelectionGroup() == null)
332     {
333       SequenceGroup sg = new SequenceGroup();
334       sg.setStartRes(seqCanvas.cursorX);
335       sg.setEndRes(seqCanvas.cursorX);
336       sg.addSequence(sequence, false);
337       av.setSelectionGroup(sg);
338     }
339     ap.paintAlignment(false);
340     av.sendSelection();
341   }
342
343   void insertGapAtCursor(boolean group)
344   {
345     groupEditing = group;
346     startseq = seqCanvas.cursorY;
347     lastres = seqCanvas.cursorX;
348     editSequence(true, seqCanvas.cursorX + getKeyboardNo1());
349     endEditing();
350   }
351
352   void deleteGapAtCursor(boolean group)
353   {
354     groupEditing = group;
355     startseq = seqCanvas.cursorY;
356     lastres = seqCanvas.cursorX + getKeyboardNo1();
357     editSequence(false, seqCanvas.cursorX);
358     endEditing();
359   }
360
361   void numberPressed(char value)
362   {
363     if (keyboardNo1 == null)
364     {
365       keyboardNo1 = new StringBuffer();
366     }
367
368     if (keyboardNo2 != null)
369     {
370       keyboardNo2.append(value);
371     }
372     else
373     {
374       keyboardNo1.append(value);
375     }
376   }
377
378   int getKeyboardNo1()
379   {
380     try
381     {
382       if (keyboardNo1 != null)
383       {
384         int value = Integer.parseInt(keyboardNo1.toString());
385         keyboardNo1 = null;
386         return value;
387       }
388     } catch (Exception x)
389     {
390     }
391     keyboardNo1 = null;
392     return 1;
393   }
394
395   int getKeyboardNo2()
396   {
397     try
398     {
399       if (keyboardNo2 != null)
400       {
401         int value = Integer.parseInt(keyboardNo2.toString());
402         keyboardNo2 = null;
403         return value;
404       }
405     } catch (Exception x)
406     {
407     }
408     keyboardNo2 = null;
409     return 1;
410   }
411
412   /**
413    * Set status message in alignment panel
414    * 
415    * @param sequence
416    *          aligned sequence object
417    * @param column
418    *          alignment column
419    * @param seq
420    *          index of sequence in alignment
421    * @return position of column in sequence or -1 if at gap
422    */
423   void setStatusMessage(SequenceI sequence, int column, int seq)
424   {
425     // TODO remove duplication of identical gui method
426     StringBuilder text = new StringBuilder(32);
427     String seqno = seq == -1 ? "" : " " + (seq + 1);
428     text.append("Sequence" + seqno + " ID: " + sequence.getName());
429
430     String residue = null;
431     /*
432      * Try to translate the display character to residue name (null for gap).
433      */
434     final String displayChar = String.valueOf(sequence.getCharAt(column));
435     if (av.getAlignment().isNucleotide())
436     {
437       residue = ResidueProperties.nucleotideName.get(displayChar);
438       if (residue != null)
439       {
440         text.append(" Nucleotide: ").append(residue);
441       }
442     }
443     else
444     {
445       residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*"
446               .equals(displayChar) ? "STOP" : ResidueProperties.aa2Triplet
447               .get(displayChar));
448       if (residue != null)
449       {
450         text.append(" Residue: ").append(residue);
451       }
452     }
453
454     int pos = -1;
455     if (residue != null)
456     {
457       pos = sequence.findPosition(column);
458       text.append(" (").append(Integer.toString(pos)).append(")");
459     }
460
461     ap.alignFrame.statusBar.setText(text.toString());
462   }
463
464   /**
465    * Set the status bar message to highlight the first matched position in
466    * search results.
467    * 
468    * @param results
469    * @return true if results were matched, false if not
470    */
471   private boolean setStatusMessage(SearchResultsI results)
472   {
473     AlignmentI al = this.av.getAlignment();
474     int sequenceIndex = al.findIndex(results);
475     if (sequenceIndex == -1)
476     {
477       return false;
478     }
479     SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
480     for (SearchResultMatchI m : results.getResults())
481     {
482       SequenceI seq = m.getSequence();
483       if (seq.getDatasetSequence() != null)
484       {
485         seq = seq.getDatasetSequence();
486       }
487
488       if (seq == ds)
489       {
490         /*
491          * Convert position in sequence (base 1) to sequence character array
492          * index (base 0)
493          */
494         int start = m.getStart() - m.getSequence().getStart();
495         setStatusMessage(seq, start, sequenceIndex);
496         return true;
497       }
498     }
499     return false;
500   }
501
502   @Override
503   public void mousePressed(MouseEvent evt)
504   {
505     lastMousePress = evt.getPoint();
506
507     // For now, ignore the mouseWheel font resizing on Macs
508     // As the Button2_mask always seems to be true
509     if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK
510             && !av.MAC)
511     {
512       mouseWheelPressed = true;
513       return;
514     }
515
516     if (evt.isShiftDown() || evt.isControlDown() || evt.isAltDown())
517     {
518       if (evt.isControlDown() || evt.isAltDown())
519       {
520         groupEditing = true;
521       }
522       editingSeqs = true;
523     }
524     else
525     {
526       doMousePressedDefineMode(evt);
527       return;
528     }
529
530     int seq = findSeq(evt);
531     int res = findRes(evt);
532
533     if (seq < 0 || res < 0)
534     {
535       return;
536     }
537
538     if ((seq < av.getAlignment().getHeight())
539             && (res < av.getAlignment().getSequenceAt(seq).getLength()))
540     {
541       startseq = seq;
542       lastres = res;
543     }
544     else
545     {
546       startseq = -1;
547       lastres = -1;
548     }
549
550     return;
551   }
552
553   @Override
554   public void mouseClicked(MouseEvent evt)
555   {
556     SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
557     if (evt.getClickCount() > 1)
558     {
559       if (av.getSelectionGroup() != null
560               && av.getSelectionGroup().getSize() == 1
561               && av.getSelectionGroup().getEndRes()
562                       - av.getSelectionGroup().getStartRes() < 2)
563       {
564         av.setSelectionGroup(null);
565       }
566
567       List<SequenceFeature> features = findFeaturesAtRes(sequence,
568               sequence.findPosition(findRes(evt)));
569
570       if (!features.isEmpty())
571       {
572         SearchResultsI highlight = new SearchResults();
573         highlight.addResult(sequence, features.get(0).getBegin(), features
574                 .get(0).getEnd());
575         seqCanvas.highlightSearchResults(highlight);
576         SequenceFeature[] featuresArray = features
577                 .toArray(new SequenceFeature[features.size()]);
578         seqCanvas.getFeatureRenderer().amendFeatures(
579                 new SequenceI[] { sequence }, featuresArray, false, ap);
580
581         seqCanvas.highlightSearchResults(null);
582       }
583     }
584   }
585
586   @Override
587   public void mouseReleased(MouseEvent evt)
588   {
589     mouseDragging = false;
590     mouseWheelPressed = false;
591     ap.paintAlignment(true);
592
593     if (!editingSeqs)
594     {
595       doMouseReleasedDefineMode(evt);
596       return;
597     }
598
599     endEditing();
600
601   }
602
603   int startWrapBlock = -1;
604
605   int wrappedBlock = -1;
606
607   int findRes(MouseEvent evt)
608   {
609     int res = 0;
610     int x = evt.getX();
611
612     if (av.getWrapAlignment())
613     {
614
615       int hgap = av.getCharHeight();
616       if (av.getScaleAboveWrapped())
617       {
618         hgap += av.getCharHeight();
619       }
620
621       int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
622               + hgap + seqCanvas.getAnnotationHeight();
623
624       int y = evt.getY();
625       y -= hgap;
626       x -= seqCanvas.LABEL_WEST;
627
628       int cwidth = seqCanvas.getWrappedCanvasWidth(getSize().width);
629       if (cwidth < 1)
630       {
631         return 0;
632       }
633
634       wrappedBlock = y / cHeight;
635       wrappedBlock += av.getRanges().getStartRes() / cwidth;
636
637       res = wrappedBlock * cwidth + x / av.getCharWidth();
638
639     }
640     else
641     {
642       res = (x / av.getCharWidth()) + av.getRanges().getStartRes();
643     }
644
645     if (av.hasHiddenColumns())
646     {
647       res = av.getAlignment().getHiddenColumns()
648               .adjustForHiddenColumns(res);
649     }
650
651     return res;
652
653   }
654
655   int findSeq(MouseEvent evt)
656   {
657     final int sqnum = findAlRow(evt);
658     return (sqnum < 0) ? 0 : sqnum;
659   }
660
661   /**
662    * 
663    * @param evt
664    * @return row in alignment that was selected (or -1 for column selection)
665    */
666   private int findAlRow(MouseEvent evt)
667   {
668     int seq = 0;
669     int y = evt.getY();
670
671     if (av.getWrapAlignment())
672     {
673       int hgap = av.getCharHeight();
674       if (av.getScaleAboveWrapped())
675       {
676         hgap += av.getCharHeight();
677       }
678
679       int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
680               + hgap + seqCanvas.getAnnotationHeight();
681
682       y -= hgap;
683
684       seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment()
685               .getHeight() - 1);
686       if (seq < 0)
687       {
688         seq = -1;
689       }
690     }
691     else
692     {
693       seq = Math.min((y / av.getCharHeight())
694               + av.getRanges().getStartSeq(),
695               av
696               .getAlignment().getHeight() - 1);
697       if (seq < 0)
698       {
699         seq = -1;
700       }
701     }
702
703     return seq;
704   }
705
706   public void doMousePressed(MouseEvent evt)
707   {
708
709     int seq = findSeq(evt);
710     int res = findRes(evt);
711
712     if (seq < av.getAlignment().getHeight()
713             && res < av.getAlignment().getSequenceAt(seq).getLength())
714     {
715       // char resstr = align.getSequenceAt(seq).getSequence().charAt(res);
716       // Find the residue's position in the sequence (res is the position
717       // in the alignment
718
719       startseq = seq;
720       lastres = res;
721     }
722     else
723     {
724       startseq = -1;
725       lastres = -1;
726     }
727
728     return;
729   }
730
731   String lastMessage;
732
733   @Override
734   public void mouseOverSequence(SequenceI sequence, int index, int pos)
735   {
736     String tmp = sequence.hashCode() + index + "";
737     if (lastMessage == null || !lastMessage.equals(tmp))
738     {
739       ssm.mouseOverSequence(sequence, index, pos, av);
740     }
741
742     lastMessage = tmp;
743   }
744
745   @Override
746   public void highlightSequence(SearchResultsI results)
747   {
748     if (av.isFollowHighlight())
749     {
750       // don't allow highlight of protein/cDNA to also scroll a complementary
751       // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
752       // over residue to change abruptly, causing highlighted residue in panel 2
753       // to change, causing a scroll in panel 1 etc)
754       ap.setToScrollComplementPanel(false);
755       if (ap.scrollToPosition(results, true))
756       {
757         ap.alignFrame.repaint();
758       }
759       ap.setToScrollComplementPanel(true);
760     }
761     setStatusMessage(results);
762     seqCanvas.highlightSearchResults(results);
763
764   }
765
766   @Override
767   public VamsasSource getVamsasSource()
768   {
769     return this.ap == null ? null : this.ap.av;
770   }
771
772   @Override
773   public void updateColours(SequenceI seq, int index)
774   {
775     System.out.println("update the seqPanel colours");
776     // repaint();
777   }
778
779   @Override
780   public void mouseMoved(MouseEvent evt)
781   {
782     final int column = findRes(evt);
783     int seq = findSeq(evt);
784
785     if (seq >= av.getAlignment().getHeight() || seq < 0 || column < 0)
786     {
787       if (tooltip != null)
788       {
789         tooltip.setTip("");
790       }
791       return;
792     }
793
794     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
795     if (column > sequence.getLength())
796     {
797       if (tooltip != null)
798       {
799         tooltip.setTip("");
800       }
801       return;
802     }
803
804     final char ch = sequence.getCharAt(column);
805     int respos = Comparison.isGap(ch) ? -1 : sequence.findPosition(column);
806
807     if (ssm != null && respos != -1)
808     {
809       mouseOverSequence(sequence, column, respos);
810     }
811
812     StringBuilder text = new StringBuilder();
813     text.append("Sequence ").append(Integer.toString(seq + 1))
814             .append(" ID: ").append(sequence.getName());
815
816     String obj = null;
817     if (respos != -1)
818     {
819       if (av.getAlignment().isNucleotide())
820       {
821         obj = ResidueProperties.nucleotideName.get(ch);
822         if (obj != null)
823         {
824           text.append(" Nucleotide: ").append(obj);
825         }
826       }
827       else
828       {
829         obj = (ch == 'x' || ch == 'X') ? "X" : ResidueProperties.aa2Triplet
830                 .get(String.valueOf(ch));
831         if (obj != null)
832         {
833           text.append(" Residue: ").append(obj);
834         }
835       }
836       if (obj != null)
837       {
838         text.append(" (").append(Integer.toString(respos)).append(")");
839       }
840     }
841
842     ap.alignFrame.statusBar.setText(text.toString());
843
844     StringBuilder tooltipText = new StringBuilder();
845     SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
846     if (groups != null)
847     {
848       for (int g = 0; g < groups.length; g++)
849       {
850         if (groups[g].getStartRes() <= column && groups[g].getEndRes() >= column)
851         {
852           if (!groups[g].getName().startsWith("JTreeGroup")
853                   && !groups[g].getName().startsWith("JGroup"))
854           {
855             tooltipText.append(groups[g].getName()).append(" ");
856           }
857           if (groups[g].getDescription() != null)
858           {
859             tooltipText.append(groups[g].getDescription());
860           }
861           tooltipText.append("\n");
862         }
863       }
864     }
865
866     /*
867      * add feature details to tooltip if over one or more features
868      */
869     if (respos != -1)
870     {
871       List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
872               respos);
873       for (SequenceFeature sf : allFeatures)
874       {
875         tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
876
877         if (sf.getDescription() != null)
878         {
879           tooltipText.append(" " + sf.getDescription());
880         }
881
882         if (sf.getValue("status") != null)
883         {
884           String status = sf.getValue("status").toString();
885           if (status.length() > 0)
886           {
887             tooltipText.append(" (" + sf.getValue("status") + ")");
888           }
889         }
890         tooltipText.append("\n");
891       }
892     }
893
894     if (tooltip == null)
895     {
896       tooltip = new Tooltip(tooltipText.toString(), seqCanvas);
897     }
898     else
899     {
900       tooltip.setTip(tooltipText.toString());
901     }
902   }
903
904   List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
905   {
906     return seqCanvas.getFeatureRenderer().findFeaturesAtRes(sequence, res);
907   }
908
909   Tooltip tooltip;
910
911   /**
912    * set when the current UI interaction has resulted in a change that requires
913    * overview shading to be recalculated. this could be changed to something
914    * more expressive that indicates what actually has changed, so selective
915    * redraws can be applied
916    */
917   private boolean needOverviewUpdate; // TODO: refactor to avcontroller
918
919   @Override
920   public void mouseDragged(MouseEvent evt)
921   {
922     if (mouseWheelPressed)
923     {
924       int oldWidth = av.getCharWidth();
925
926       // Which is bigger, left-right or up-down?
927       if (Math.abs(evt.getY() - lastMousePress.y) > Math.abs(evt.getX()
928               - lastMousePress.x))
929       {
930         int fontSize = av.font.getSize();
931
932         if (evt.getY() < lastMousePress.y && av.getCharHeight() > 1)
933         {
934           fontSize--;
935         }
936         else if (evt.getY() > lastMousePress.y)
937         {
938           fontSize++;
939         }
940
941         if (fontSize < 1)
942         {
943           fontSize = 1;
944         }
945
946         av.setFont(
947                 new Font(av.font.getName(), av.font.getStyle(), fontSize),
948                 true);
949         av.setCharWidth(oldWidth);
950       }
951       else
952       {
953         if (evt.getX() < lastMousePress.x && av.getCharWidth() > 1)
954         {
955           av.setCharWidth(av.getCharWidth() - 1);
956         }
957         else if (evt.getX() > lastMousePress.x)
958         {
959           av.setCharWidth(av.getCharWidth() + 1);
960         }
961
962         if (av.getCharWidth() < 1)
963         {
964           av.setCharWidth(1);
965         }
966       }
967
968       ap.fontChanged();
969
970       FontMetrics fm = getFontMetrics(av.getFont());
971       av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
972
973       lastMousePress = evt.getPoint();
974
975       ap.paintAlignment(false);
976       ap.annotationPanel.image = null;
977       return;
978     }
979
980     if (!editingSeqs)
981     {
982       doMouseDraggedDefineMode(evt);
983       return;
984     }
985
986     int res = findRes(evt);
987
988     if (res < 0)
989     {
990       res = 0;
991     }
992
993     if ((lastres == -1) || (lastres == res))
994     {
995       return;
996     }
997
998     if ((res < av.getAlignment().getWidth()) && (res < lastres))
999     {
1000       // dragLeft, delete gap
1001       editSequence(false, res);
1002     }
1003     else
1004     {
1005       editSequence(true, res);
1006     }
1007
1008     mouseDragging = true;
1009     if (scrollThread != null)
1010     {
1011       scrollThread.setEvent(evt);
1012     }
1013
1014   }
1015
1016   synchronized void editSequence(boolean insertGap, int startres)
1017   {
1018     int fixedLeft = -1;
1019     int fixedRight = -1;
1020     boolean fixedColumns = false;
1021     SequenceGroup sg = av.getSelectionGroup();
1022
1023     SequenceI seq = av.getAlignment().getSequenceAt(startseq);
1024
1025     if (!groupEditing && av.hasHiddenRows())
1026     {
1027       if (av.isHiddenRepSequence(seq))
1028       {
1029         sg = av.getRepresentedSequences(seq);
1030         groupEditing = true;
1031       }
1032     }
1033
1034     StringBuffer message = new StringBuffer();
1035     if (groupEditing)
1036     {
1037       message.append(MessageManager.getString("action.edit_group")).append(
1038               ":");
1039       if (editCommand == null)
1040       {
1041         editCommand = new EditCommand(
1042                 MessageManager.getString("action.edit_group"));
1043       }
1044     }
1045     else
1046     {
1047       message.append(MessageManager.getString("label.edit_sequence"))
1048               .append(" " + seq.getName());
1049       String label = seq.getName();
1050       if (label.length() > 10)
1051       {
1052         label = label.substring(0, 10);
1053       }
1054       if (editCommand == null)
1055       {
1056         editCommand = new EditCommand(MessageManager.formatMessage(
1057                 "label.edit_params", new String[] { label }));
1058       }
1059     }
1060
1061     if (insertGap)
1062     {
1063       message.append(" insert ");
1064     }
1065     else
1066     {
1067       message.append(" delete ");
1068     }
1069
1070     message.append(Math.abs(startres - lastres) + " gaps.");
1071     ap.alignFrame.statusBar.setText(message.toString());
1072
1073     // Are we editing within a selection group?
1074     if (groupEditing
1075             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
1076                     .contains(seq)))
1077     {
1078       fixedColumns = true;
1079
1080       // sg might be null as the user may only see 1 sequence,
1081       // but the sequence represents a group
1082       if (sg == null)
1083       {
1084         if (!av.isHiddenRepSequence(seq))
1085         {
1086           endEditing();
1087           return;
1088         }
1089
1090         sg = av.getRepresentedSequences(seq);
1091       }
1092
1093       fixedLeft = sg.getStartRes();
1094       fixedRight = sg.getEndRes();
1095
1096       if ((startres < fixedLeft && lastres >= fixedLeft)
1097               || (startres >= fixedLeft && lastres < fixedLeft)
1098               || (startres > fixedRight && lastres <= fixedRight)
1099               || (startres <= fixedRight && lastres > fixedRight))
1100       {
1101         endEditing();
1102         return;
1103       }
1104
1105       if (fixedLeft > startres)
1106       {
1107         fixedRight = fixedLeft - 1;
1108         fixedLeft = 0;
1109       }
1110       else if (fixedRight < startres)
1111       {
1112         fixedLeft = fixedRight;
1113         fixedRight = -1;
1114       }
1115     }
1116
1117     if (av.hasHiddenColumns())
1118     {
1119       fixedColumns = true;
1120       int y1 = av.getAlignment().getHiddenColumns()
1121               .getHiddenBoundaryLeft(startres);
1122       int y2 = av.getAlignment().getHiddenColumns()
1123               .getHiddenBoundaryRight(startres);
1124
1125       if ((insertGap && startres > y1 && lastres < y1)
1126               || (!insertGap && startres < y2 && lastres > y2))
1127       {
1128         endEditing();
1129         return;
1130       }
1131
1132       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1133       // Selection spans a hidden region
1134       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1135       {
1136         if (startres >= y2)
1137         {
1138           fixedLeft = y2;
1139         }
1140         else
1141         {
1142           fixedRight = y2 - 1;
1143         }
1144       }
1145     }
1146
1147     if (groupEditing)
1148     {
1149       SequenceI[] groupSeqs = sg.getSequences(av.getHiddenRepSequences())
1150               .toArray(new SequenceI[0]);
1151
1152       // drag to right
1153       if (insertGap)
1154       {
1155         // If the user has selected the whole sequence, and is dragging to
1156         // the right, we can still extend the alignment and selectionGroup
1157         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1158                 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1159         {
1160           sg.setEndRes(av.getAlignment().getWidth() + startres - lastres);
1161           fixedRight = sg.getEndRes();
1162         }
1163
1164         // Is it valid with fixed columns??
1165         // Find the next gap before the end
1166         // of the visible region boundary
1167         boolean blank = false;
1168         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1169         {
1170           blank = true;
1171
1172           for (SequenceI gs : groupSeqs)
1173           {
1174             for (int j = 0; j < startres - lastres; j++)
1175             {
1176               if (!jalview.util.Comparison.isGap(gs.getCharAt(fixedRight
1177                       - j)))
1178               {
1179                 blank = false;
1180                 break;
1181               }
1182             }
1183           }
1184           if (blank)
1185           {
1186             break;
1187           }
1188         }
1189
1190         if (!blank)
1191         {
1192           if (sg.getSize() == av.getAlignment().getHeight())
1193           {
1194             if ((av.hasHiddenColumns() && startres < av.getAlignment()
1195                     .getHiddenColumns().getHiddenBoundaryRight(startres)))
1196             {
1197               endEditing();
1198               return;
1199             }
1200
1201             int alWidth = av.getAlignment().getWidth();
1202             if (av.hasHiddenRows())
1203             {
1204               int hwidth = av.getAlignment().getHiddenSequences()
1205                       .getWidth();
1206               if (hwidth > alWidth)
1207               {
1208                 alWidth = hwidth;
1209               }
1210             }
1211             // We can still insert gaps if the selectionGroup
1212             // contains all the sequences
1213             sg.setEndRes(sg.getEndRes() + startres - lastres);
1214             fixedRight = alWidth + startres - lastres;
1215           }
1216           else
1217           {
1218             endEditing();
1219             return;
1220           }
1221         }
1222       }
1223
1224       // drag to left
1225       else if (!insertGap)
1226       {
1227         // / Are we able to delete?
1228         // ie are all columns blank?
1229
1230         for (SequenceI gs : groupSeqs)
1231         {
1232           for (int j = startres; j < lastres; j++)
1233           {
1234             if (gs.getLength() <= j)
1235             {
1236               continue;
1237             }
1238
1239             if (!jalview.util.Comparison.isGap(gs.getCharAt(j)))
1240             {
1241               // Not a gap, block edit not valid
1242               endEditing();
1243               return;
1244             }
1245           }
1246         }
1247       }
1248
1249       if (insertGap)
1250       {
1251         // dragging to the right
1252         if (fixedColumns && fixedRight != -1)
1253         {
1254           for (int j = lastres; j < startres; j++)
1255           {
1256             insertChar(j, groupSeqs, fixedRight);
1257           }
1258         }
1259         else
1260         {
1261           editCommand.appendEdit(Action.INSERT_GAP, groupSeqs, startres,
1262                   startres - lastres, av.getAlignment(), true);
1263         }
1264       }
1265       else
1266       {
1267         // dragging to the left
1268         if (fixedColumns && fixedRight != -1)
1269         {
1270           for (int j = lastres; j > startres; j--)
1271           {
1272             deleteChar(startres, groupSeqs, fixedRight);
1273           }
1274         }
1275         else
1276         {
1277           editCommand.appendEdit(Action.DELETE_GAP, groupSeqs, startres,
1278                   lastres - startres, av.getAlignment(), true);
1279         }
1280
1281       }
1282     }
1283     else
1284     // ///Editing a single sequence///////////
1285     {
1286       if (insertGap)
1287       {
1288         // dragging to the right
1289         if (fixedColumns && fixedRight != -1)
1290         {
1291           for (int j = lastres; j < startres; j++)
1292           {
1293             insertChar(j, new SequenceI[] { seq }, fixedRight);
1294           }
1295         }
1296         else
1297         {
1298           editCommand.appendEdit(Action.INSERT_GAP,
1299                   new SequenceI[] { seq }, lastres, startres - lastres,
1300                   av.getAlignment(), true);
1301         }
1302       }
1303       else
1304       {
1305         // dragging to the left
1306         if (fixedColumns && fixedRight != -1)
1307         {
1308           for (int j = lastres; j > startres; j--)
1309           {
1310             if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1311             {
1312               endEditing();
1313               break;
1314             }
1315             deleteChar(startres, new SequenceI[] { seq }, fixedRight);
1316           }
1317         }
1318         else
1319         {
1320           // could be a keyboard edit trying to delete none gaps
1321           int max = 0;
1322           for (int m = startres; m < lastres; m++)
1323           {
1324             if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1325             {
1326               break;
1327             }
1328             max++;
1329           }
1330
1331           if (max > 0)
1332           {
1333             editCommand.appendEdit(Action.DELETE_GAP,
1334                     new SequenceI[] { seq }, startres, max,
1335                     av.getAlignment(), true);
1336           }
1337         }
1338       }
1339     }
1340
1341     lastres = startres;
1342     seqCanvas.repaint();
1343   }
1344
1345   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1346   {
1347     int blankColumn = fixedColumn;
1348     for (int s = 0; s < seq.length; s++)
1349     {
1350       // Find the next gap before the end of the visible region boundary
1351       // If lastCol > j, theres a boundary after the gap insertion
1352
1353       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1354       {
1355         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1356         {
1357           // Theres a space, so break and insert the gap
1358           break;
1359         }
1360       }
1361
1362       if (blankColumn <= j)
1363       {
1364         blankColumn = fixedColumn;
1365         endEditing();
1366         return;
1367       }
1368     }
1369
1370     editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1,
1371             av.getAlignment(), true);
1372
1373     editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1, av.getAlignment(),
1374             true);
1375
1376   }
1377
1378   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1379   {
1380
1381     editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1, av.getAlignment(),
1382             true);
1383
1384     editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1,
1385             av.getAlignment(), true);
1386   }
1387
1388   // ////////////////////////////////////////
1389   // ///Everything below this is for defining the boundary of the rubberband
1390   // ////////////////////////////////////////
1391   public void doMousePressedDefineMode(MouseEvent evt)
1392   {
1393     if (scrollThread != null)
1394     {
1395       scrollThread.running = false;
1396       scrollThread = null;
1397     }
1398
1399     int res = findRes(evt);
1400     int seq = findSeq(evt);
1401     oldSeq = seq;
1402     startWrapBlock = wrappedBlock;
1403
1404     if (seq == -1)
1405     {
1406       return;
1407     }
1408
1409     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
1410
1411     if (sequence == null || res > sequence.getLength())
1412     {
1413       return;
1414     }
1415
1416     stretchGroup = av.getSelectionGroup();
1417
1418     if (stretchGroup == null || !stretchGroup.contains(sequence, res))
1419     {
1420       stretchGroup = av.getAlignment().findGroup(sequence, res);
1421       if (stretchGroup != null)
1422       {
1423         // only update the current selection if the popup menu has a group to
1424         // focus on
1425         av.setSelectionGroup(stretchGroup);
1426       }
1427     }
1428
1429     // DETECT RIGHT MOUSE BUTTON IN AWT
1430     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
1431     {
1432       List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
1433               sequence.findPosition(res));
1434
1435       Vector<String> links = null;
1436       for (int i = 0; i < allFeatures.size(); i++)
1437       {
1438         SequenceFeature sf = allFeatures.get(i);
1439         if (sf.links != null)
1440         {
1441           if (links == null)
1442           {
1443             links = new Vector<String>();
1444           }
1445           links.addAll(sf.links);
1446         }
1447       }
1448       APopupMenu popup = new APopupMenu(ap, null, links);
1449       this.add(popup);
1450       popup.show(this, evt.getX(), evt.getY());
1451       return;
1452     }
1453
1454     if (av.cursorMode)
1455     {
1456       seqCanvas.cursorX = findRes(evt);
1457       seqCanvas.cursorY = findSeq(evt);
1458       seqCanvas.repaint();
1459       return;
1460     }
1461
1462     // Only if left mouse button do we want to change group sizes
1463
1464     if (stretchGroup == null)
1465     {
1466       // define a new group here
1467       SequenceGroup sg = new SequenceGroup();
1468       sg.setStartRes(res);
1469       sg.setEndRes(res);
1470       sg.addSequence(sequence, false);
1471       av.setSelectionGroup(sg);
1472       stretchGroup = sg;
1473
1474       if (av.getConservationSelected())
1475       {
1476         SliderPanel.setConservationSlider(ap, av.getResidueShading(),
1477                 ap.getViewName());
1478       }
1479       if (av.getAbovePIDThreshold())
1480       {
1481         SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
1482                 ap.getViewName());
1483       }
1484
1485     }
1486   }
1487
1488   public void doMouseReleasedDefineMode(MouseEvent evt)
1489   {
1490     if (stretchGroup == null)
1491     {
1492       return;
1493     }
1494     // always do this - annotation has own state
1495     // but defer colourscheme update until hidden sequences are passed in
1496     boolean vischange = stretchGroup.recalcConservation(true);
1497     // here we rely on stretchGroup == av.getSelection()
1498     needOverviewUpdate |= vischange && av.isSelectionDefinedGroup();
1499     if (stretchGroup.cs != null)
1500     {
1501       stretchGroup.cs.alignmentChanged(stretchGroup,
1502               av.getHiddenRepSequences());
1503
1504       if (stretchGroup.cs.conservationApplied())
1505       {
1506         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1507                 stretchGroup.getName());
1508       }
1509       if (stretchGroup.cs.getThreshold() > 0)
1510       {
1511         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1512                 stretchGroup.getName());
1513       }
1514     }
1515     PaintRefresher.Refresh(ap, av.getSequenceSetId());
1516     ap.paintAlignment(needOverviewUpdate);
1517     needOverviewUpdate = false;
1518     changeEndRes = false;
1519     changeStartRes = false;
1520     stretchGroup = null;
1521     av.sendSelection();
1522   }
1523
1524   public void doMouseDraggedDefineMode(MouseEvent evt)
1525   {
1526     int res = findRes(evt);
1527     int y = findSeq(evt);
1528
1529     if (wrappedBlock != startWrapBlock)
1530     {
1531       return;
1532     }
1533
1534     if (stretchGroup == null)
1535     {
1536       return;
1537     }
1538
1539     mouseDragging = true;
1540
1541     if (y > av.getAlignment().getHeight())
1542     {
1543       y = av.getAlignment().getHeight() - 1;
1544     }
1545
1546     if (res >= av.getAlignment().getWidth())
1547     {
1548       res = av.getAlignment().getWidth() - 1;
1549     }
1550
1551     if (stretchGroup.getEndRes() == res)
1552     {
1553       // Edit end res position of selected group
1554       changeEndRes = true;
1555     }
1556     else if (stretchGroup.getStartRes() == res)
1557     {
1558       // Edit start res position of selected group
1559       changeStartRes = true;
1560     }
1561
1562     if (res < 0)
1563     {
1564       res = 0;
1565     }
1566
1567     if (changeEndRes)
1568     {
1569       if (res > (stretchGroup.getStartRes() - 1))
1570       {
1571         stretchGroup.setEndRes(res);
1572         needOverviewUpdate |= av.isSelectionDefinedGroup();
1573       }
1574     }
1575     else if (changeStartRes)
1576     {
1577       if (res < (stretchGroup.getEndRes() + 1))
1578       {
1579         stretchGroup.setStartRes(res);
1580         needOverviewUpdate |= av.isSelectionDefinedGroup();
1581       }
1582     }
1583
1584     int dragDirection = 0;
1585
1586     if (y > oldSeq)
1587     {
1588       dragDirection = 1;
1589     }
1590     else if (y < oldSeq)
1591     {
1592       dragDirection = -1;
1593     }
1594
1595     while ((y != oldSeq) && (oldSeq > -1)
1596             && (y < av.getAlignment().getHeight()))
1597     {
1598       // This routine ensures we don't skip any sequences, as the
1599       // selection is quite slow.
1600       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1601
1602       oldSeq += dragDirection;
1603
1604       if (oldSeq < 0)
1605       {
1606         break;
1607       }
1608
1609       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1610
1611       if (stretchGroup.getSequences(null).contains(nextSeq))
1612       {
1613         stretchGroup.deleteSequence(seq, false);
1614         needOverviewUpdate |= av.isSelectionDefinedGroup();
1615       }
1616       else
1617       {
1618         if (seq != null)
1619         {
1620           stretchGroup.addSequence(seq, false);
1621         }
1622
1623         stretchGroup.addSequence(nextSeq, false);
1624         needOverviewUpdate |= av.isSelectionDefinedGroup();
1625       }
1626     }
1627
1628     if (oldSeq < 0)
1629     {
1630       oldSeq = -1;
1631     }
1632
1633     if (res > av.getRanges().getEndRes()
1634             || res < av.getRanges().getStartRes()
1635             || y < av.getRanges().getStartSeq()
1636             || y > av.getRanges().getEndSeq())
1637     {
1638       mouseExited(evt);
1639     }
1640
1641     if (scrollThread != null)
1642     {
1643       scrollThread.setEvent(evt);
1644     }
1645
1646     seqCanvas.repaint();
1647   }
1648
1649   @Override
1650   public void mouseEntered(MouseEvent e)
1651   {
1652     if (oldSeq < 0)
1653     {
1654       oldSeq = 0;
1655     }
1656
1657     if (scrollThread != null)
1658     {
1659       scrollThread.running = false;
1660       scrollThread = null;
1661     }
1662   }
1663
1664   @Override
1665   public void mouseExited(MouseEvent e)
1666   {
1667     if (av.getWrapAlignment())
1668     {
1669       return;
1670     }
1671
1672     if (mouseDragging && scrollThread == null)
1673     {
1674       scrollThread = new ScrollThread();
1675     }
1676   }
1677
1678   void scrollCanvas(MouseEvent evt)
1679   {
1680     if (evt == null)
1681     {
1682       if (scrollThread != null)
1683       {
1684         scrollThread.running = false;
1685         scrollThread = null;
1686       }
1687       mouseDragging = false;
1688     }
1689     else
1690     {
1691       if (scrollThread == null)
1692       {
1693         scrollThread = new ScrollThread();
1694       }
1695
1696       mouseDragging = true;
1697       scrollThread.setEvent(evt);
1698     }
1699
1700   }
1701
1702   // this class allows scrolling off the bottom of the visible alignment
1703   class ScrollThread extends Thread
1704   {
1705     MouseEvent evt;
1706
1707     boolean running = false;
1708
1709     public ScrollThread()
1710     {
1711       start();
1712     }
1713
1714     public void setEvent(MouseEvent e)
1715     {
1716       evt = e;
1717     }
1718
1719     public void stopScrolling()
1720     {
1721       running = false;
1722     }
1723
1724     @Override
1725     public void run()
1726     {
1727       running = true;
1728       while (running)
1729       {
1730
1731         if (evt != null)
1732         {
1733
1734           if (mouseDragging && evt.getY() < 0
1735                   && av.getRanges().getStartSeq() > 0)
1736           {
1737             running = av.getRanges().scrollUp(true);
1738           }
1739
1740           if (mouseDragging && evt.getY() >= getSize().height
1741                   && av.getAlignment().getHeight() > av.getRanges()
1742                           .getEndSeq())
1743           {
1744             running = av.getRanges().scrollUp(false);
1745           }
1746
1747           if (mouseDragging && evt.getX() < 0)
1748           {
1749             running = av.getRanges().scrollRight(false);
1750           }
1751
1752           else if (mouseDragging && evt.getX() >= getSize().width)
1753           {
1754             running = av.getRanges().scrollRight(true);
1755           }
1756         }
1757
1758         try
1759         {
1760           Thread.sleep(75);
1761         } catch (Exception ex)
1762         {
1763         }
1764       }
1765     }
1766   }
1767
1768   /**
1769    * modify current selection according to a received message.
1770    */
1771   @Override
1772   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1773           HiddenColumns hidden, SelectionSource source)
1774   {
1775     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
1776     // handles selection messages...
1777     // TODO: extend config options to allow user to control if selections may be
1778     // shared between viewports.
1779     if (av != null
1780             && (av == source || !av.followSelection || (source instanceof AlignViewport && ((AlignmentViewport) source)
1781                     .getSequenceSetId().equals(av.getSequenceSetId()))))
1782     {
1783       return;
1784     }
1785
1786     /*
1787      * Check for selection in a view of which this one is a dna/protein
1788      * complement.
1789      */
1790     if (selectionFromTranslation(seqsel, colsel, hidden, source))
1791     {
1792       return;
1793     }
1794
1795     // do we want to thread this ? (contention with seqsel and colsel locks, I
1796     // suspect)
1797     /*
1798      * only copy colsel if there is a real intersection between
1799      * sequence selection and this panel's alignment
1800      */
1801     boolean repaint = false;
1802     boolean copycolsel = false;
1803     if (av.getSelectionGroup() == null || !av.isSelectionGroupChanged(true))
1804     {
1805       SequenceGroup sgroup = null;
1806       if (seqsel != null && seqsel.getSize() > 0)
1807       {
1808         if (av.getAlignment() == null)
1809         {
1810           System.out
1811                   .println("Selection message: alignviewport av SeqSetId="
1812                           + av.getSequenceSetId() + " ViewId="
1813                           + av.getViewId()
1814                           + " 's alignment is NULL! returning immediatly.");
1815           return;
1816         }
1817         sgroup = seqsel.intersect(av.getAlignment(),
1818                 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
1819         if ((sgroup != null && sgroup.getSize() > 0))
1820         {
1821           copycolsel = true;
1822         }
1823       }
1824       if (sgroup != null && sgroup.getSize() > 0)
1825       {
1826         av.setSelectionGroup(sgroup);
1827       }
1828       else
1829       {
1830         av.setSelectionGroup(null);
1831       }
1832       repaint = av.isSelectionGroupChanged(true);
1833     }
1834     if (copycolsel
1835             && (av.getColumnSelection() == null || !av
1836                     .isColSelChanged(true)))
1837     {
1838       // the current selection is unset or from a previous message
1839       // so import the new colsel.
1840       if (colsel == null || colsel.isEmpty())
1841       {
1842         if (av.getColumnSelection() != null)
1843         {
1844           av.getColumnSelection().clear();
1845         }
1846       }
1847       else
1848       {
1849         // TODO: shift colSel according to the intersecting sequences
1850         if (av.getColumnSelection() == null)
1851         {
1852           av.setColumnSelection(new ColumnSelection(colsel));
1853         }
1854         else
1855         {
1856           av.getColumnSelection().setElementsFrom(colsel,
1857                   av.getAlignment().getHiddenColumns());
1858         }
1859       }
1860       repaint |= av.isColSelChanged(true);
1861     }
1862     if (copycolsel
1863             && av.hasHiddenColumns()
1864             && (av.getColumnSelection() == null || av.getAlignment()
1865                     .getHiddenColumns().getHiddenRegions() == null))
1866     {
1867       System.err.println("Bad things");
1868     }
1869     if (repaint)
1870     {
1871       ap.scalePanelHolder.repaint();
1872       ap.repaint();
1873     }
1874   }
1875
1876   /**
1877    * scroll to the given row/column - or nearest visible location
1878    * 
1879    * @param row
1880    * @param column
1881    */
1882   public void scrollTo(int row, int column)
1883   {
1884
1885     row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
1886     column = column < 0 ? ap.av.getRanges().getStartRes() : column;
1887     ap.scrollTo(column, column, row, true, true);
1888   }
1889
1890   /**
1891    * scroll to the given row - or nearest visible location
1892    * 
1893    * @param row
1894    */
1895   public void scrollToRow(int row)
1896   {
1897
1898     row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
1899     ap.scrollTo(ap.av.getRanges().getStartRes(), ap.av.getRanges()
1900             .getStartRes(), row, true, true);
1901   }
1902
1903   /**
1904    * scroll to the given column - or nearest visible location
1905    * 
1906    * @param column
1907    */
1908   public void scrollToColumn(int column)
1909   {
1910
1911     column = column < 0 ? ap.av.getRanges().getStartRes() : column;
1912     ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true, true);
1913   }
1914
1915   /**
1916    * If this panel is a cdna/protein translation view of the selection source,
1917    * tries to map the source selection to a local one, and returns true. Else
1918    * returns false.
1919    * 
1920    * @param seqsel
1921    * @param colsel
1922    * @param source
1923    */
1924   protected boolean selectionFromTranslation(SequenceGroup seqsel,
1925           ColumnSelection colsel, HiddenColumns hidden,
1926           SelectionSource source)
1927   {
1928     if (!(source instanceof AlignViewportI))
1929     {
1930       return false;
1931     }
1932     final AlignViewportI sourceAv = (AlignViewportI) source;
1933     if (sourceAv.getCodingComplement() != av
1934             && av.getCodingComplement() != sourceAv)
1935     {
1936       return false;
1937     }
1938
1939     /*
1940      * Map sequence selection
1941      */
1942     SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
1943     av.setSelectionGroup(sg);
1944     av.isSelectionGroupChanged(true);
1945
1946     /*
1947      * Map column selection
1948      */
1949     // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
1950     // av);
1951     ColumnSelection cs = new ColumnSelection();
1952     HiddenColumns hs = new HiddenColumns();
1953     MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
1954     av.setColumnSelection(cs);
1955     av.getAlignment().setHiddenColumns(hs);
1956
1957     ap.scalePanelHolder.repaint();
1958     ap.repaint();
1959
1960     return true;
1961   }
1962
1963 }