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