JAL-345 view controller method to select highlighted sequences (note - doesn’t select...
[jalview.git] / src / jalview / controller / AlignViewController.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.controller;
22
23 import jalview.analysis.AlignmentSorter;
24 import jalview.api.AlignViewControllerGuiI;
25 import jalview.api.AlignViewControllerI;
26 import jalview.api.AlignViewportI;
27 import jalview.api.AlignmentViewPanel;
28 import jalview.api.FeatureRenderer;
29 import jalview.commands.OrderCommand;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.ColumnSelection;
32 import jalview.datamodel.SequenceCollectionI;
33 import jalview.datamodel.SequenceFeature;
34 import jalview.datamodel.SequenceGroup;
35 import jalview.datamodel.SequenceI;
36 import jalview.io.DataSourceType;
37 import jalview.io.FeaturesFile;
38 import jalview.util.MessageManager;
39
40 import java.awt.Color;
41 import java.util.ArrayList;
42 import java.util.BitSet;
43 import java.util.List;
44
45 public class AlignViewController implements AlignViewControllerI
46 {
47   AlignViewportI viewport = null;
48
49   AlignmentViewPanel alignPanel = null;
50
51   /**
52    * the GUI container that is handling interactions with the user
53    */
54   private AlignViewControllerGuiI avcg;
55
56   public AlignViewController(AlignViewControllerGuiI alignFrame,
57           AlignViewportI vp, AlignmentViewPanel ap)
58   {
59     this.avcg = alignFrame;
60     this.viewport = vp;
61     this.alignPanel = ap;
62   }
63
64   @Override
65   public void setViewportAndAlignmentPanel(AlignViewportI vp,
66           AlignmentViewPanel ap)
67   {
68     this.alignPanel = ap;
69     this.viewport = vp;
70   }
71
72   @Override
73   public boolean makeGroupsFromSelection()
74   {
75     SequenceGroup sg = viewport.getSelectionGroup();
76     ColumnSelection cs = viewport.getColumnSelection();
77     SequenceGroup[] gps = null;
78     if (sg != null && (cs == null || cs.isEmpty()))
79     {
80       gps = jalview.analysis.Grouping.makeGroupsFrom(
81               viewport.getSequenceSelection(),
82               viewport.getAlignmentView(true)
83                       .getSequenceStrings(viewport.getGapCharacter()),
84               viewport.getAlignment().getGroups());
85     }
86     else
87     {
88       if (cs != null)
89       {
90         gps = jalview.analysis.Grouping.makeGroupsFromCols(
91                 (sg == null) ? viewport.getAlignment().getSequencesArray()
92                         : sg.getSequences().toArray(new SequenceI[0]),
93                 cs, viewport.getAlignment().getGroups());
94       }
95     }
96     if (gps != null)
97     {
98       viewport.getAlignment().deleteAllGroups();
99       viewport.clearSequenceColours();
100       viewport.setSelectionGroup(null);
101       // set view properties for each group
102       for (int g = 0; g < gps.length; g++)
103       {
104         // gps[g].setShowunconserved(viewport.getShowUnconserved());
105         gps[g].setshowSequenceLogo(viewport.isShowSequenceLogo());
106         viewport.getAlignment().addGroup(gps[g]);
107         Color col = new Color((int) (Math.random() * 255),
108                 (int) (Math.random() * 255), (int) (Math.random() * 255));
109         col = col.brighter();
110         for (SequenceI sq : gps[g].getSequences(null))
111         {
112           viewport.setSequenceColour(sq, col);
113         }
114       }
115       return true;
116     }
117     return false;
118   }
119
120   @Override
121   public boolean createGroup()
122   {
123
124     SequenceGroup sg = viewport.getSelectionGroup();
125     if (sg != null)
126     {
127       viewport.getAlignment().addGroup(sg);
128       return true;
129     }
130     return false;
131   }
132
133   @Override
134   public boolean unGroup()
135   {
136     SequenceGroup sg = viewport.getSelectionGroup();
137     if (sg != null)
138     {
139       viewport.getAlignment().deleteGroup(sg);
140       return true;
141     }
142     return false;
143   }
144
145   @Override
146   public boolean deleteGroups()
147   {
148     if (viewport.getAlignment().getGroups() != null
149             && viewport.getAlignment().getGroups().size() > 0)
150     {
151       viewport.getAlignment().deleteAllGroups();
152       viewport.clearSequenceColours();
153       viewport.setSelectionGroup(null);
154       return true;
155     }
156     return false;
157   }
158
159   @Override
160   public boolean markColumnsContainingFeatures(boolean invert,
161           boolean extendCurrent, boolean toggle, String featureType)
162   {
163     // JBPNote this routine could also mark rows, not just columns.
164     // need a decent query structure to allow all types of feature searches
165     BitSet bs = new BitSet();
166     SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null
167             || extendCurrent) ? viewport.getAlignment()
168                     : viewport.getSelectionGroup();
169
170     int nseq = findColumnsWithFeature(featureType, sqcol, bs);
171
172     ColumnSelection cs = viewport.getColumnSelection();
173     if (cs == null)
174     {
175       cs = new ColumnSelection();
176     }
177
178     if (bs.cardinality() > 0 || invert)
179     {
180       boolean changed = cs.markColumns(bs, sqcol.getStartRes(),
181               sqcol.getEndRes(), invert, extendCurrent, toggle);
182       if (changed)
183       {
184         viewport.setColumnSelection(cs);
185         alignPanel.paintAlignment(false, false);
186         int columnCount = invert
187                 ? (sqcol.getEndRes() - sqcol.getStartRes() + 1)
188                         - bs.cardinality()
189                 : bs.cardinality();
190         avcg.setStatus(MessageManager.formatMessage(
191                 "label.view_controller_toggled_marked", new String[]
192                 { toggle ? MessageManager.getString("label.toggled")
193                         : MessageManager.getString("label.marked"),
194                     String.valueOf(columnCount),
195                     invert ? MessageManager
196                             .getString("label.not_containing")
197                             : MessageManager.getString("label.containing"),
198                     featureType, Integer.valueOf(nseq).toString() }));
199         return true;
200       }
201     }
202     else
203     {
204       avcg.setStatus(MessageManager
205               .formatMessage("label.no_feature_of_type_found", new String[]
206               { featureType }));
207       if (!extendCurrent)
208       {
209         cs.clear();
210         alignPanel.paintAlignment(false, false);
211       }
212     }
213     return false;
214   }
215
216   /**
217    * Sets a bit in the BitSet for each column (base 0) in the sequence
218    * collection which includes a visible feature of the specified feature type.
219    * Returns the number of sequences which have the feature visible in the
220    * selected range.
221    * 
222    * @param featureType
223    * @param sqcol
224    * @param bs
225    * @return
226    */
227   int findColumnsWithFeature(String featureType,
228           SequenceCollectionI sqcol, BitSet bs)
229   {
230     FeatureRenderer fr = alignPanel == null ? null : alignPanel
231             .getFeatureRenderer();
232
233     final int startColumn = sqcol.getStartRes() + 1; // converted to base 1
234     final int endColumn = sqcol.getEndRes() + 1;
235     List<SequenceI> seqs = sqcol.getSequences();
236     int nseq = 0;
237     for (SequenceI sq : seqs)
238     {
239       if (sq != null)
240       {
241         // int ist = sq.findPosition(sqcol.getStartRes());
242         List<SequenceFeature> sfs = sq.findFeatures(startColumn,
243                 endColumn, featureType);
244
245         boolean found = false;
246         for (SequenceFeature sf : sfs)
247         {
248           if (fr.getColour(sf) == null)
249           {
250             continue;
251           }
252           if (!found)
253           {
254             nseq++;
255           }
256           found = true;
257
258           int sfStartCol = sq.findIndex(sf.getBegin());
259           int sfEndCol = sq.findIndex(sf.getEnd());
260
261           if (sf.isContactFeature())
262           {
263             /*
264              * 'contact' feature - check for 'start' or 'end'
265              * position within the selected region
266              */
267             if (sfStartCol >= startColumn && sfStartCol <= endColumn)
268             {
269               bs.set(sfStartCol - 1);
270             }
271             if (sfEndCol >= startColumn && sfEndCol <= endColumn)
272             {
273               bs.set(sfEndCol - 1);
274             }
275             continue;
276           }
277
278           /*
279            * contiguous feature - select feature positions (if any) 
280            * within the selected region
281            */
282           if (sfStartCol < startColumn)
283           {
284             sfStartCol = startColumn;
285           }
286           // not sure what the point of this is
287           // if (sfStartCol < ist)
288           // {
289           // sfStartCol = ist;
290           // }
291           if (sfEndCol > endColumn)
292           {
293             sfEndCol = endColumn;
294           }
295           for (; sfStartCol <= sfEndCol; sfStartCol++)
296           {
297             bs.set(sfStartCol - 1); // convert to base 0
298           }
299         }
300       }
301     }
302     return nseq;
303   }
304
305   @Override
306   public void sortAlignmentByFeatureDensity(List<String> typ)
307   {
308     sortBy(typ, "Sort by Density", AlignmentSorter.FEATURE_DENSITY);
309   }
310
311   protected void sortBy(List<String> typ, String methodText,
312           final String method)
313   {
314     FeatureRenderer fr = alignPanel.getFeatureRenderer();
315     if (typ == null && fr != null)
316     {
317       typ = fr.getDisplayedFeatureTypes();
318     }
319     List<String> gps = null;
320     if (fr != null)
321     {
322       gps = fr.getDisplayedFeatureGroups();
323     }
324     AlignmentI al = viewport.getAlignment();
325
326     int start, stop;
327     SequenceGroup sg = viewport.getSelectionGroup();
328     if (sg != null)
329     {
330       start = sg.getStartRes();
331       stop = sg.getEndRes();
332     }
333     else
334     {
335       start = 0;
336       stop = al.getWidth();
337     }
338     SequenceI[] oldOrder = al.getSequencesArray();
339     AlignmentSorter.sortByFeature(typ, gps, start, stop, al, method);
340     avcg.addHistoryItem(new OrderCommand(methodText, oldOrder,
341             viewport.getAlignment()));
342     alignPanel.paintAlignment(true, false);
343
344   }
345
346   @Override
347   public void sortAlignmentByFeatureScore(List<String> typ)
348   {
349     sortBy(typ, "Sort by Feature Score", AlignmentSorter.FEATURE_SCORE);
350   }
351
352   @Override
353   public boolean parseFeaturesFile(String file, DataSourceType protocol,
354           boolean relaxedIdMatching)
355   {
356     boolean featuresAdded = false;
357     FeatureRenderer fr = alignPanel.getFeatureRenderer();
358     try
359     {
360       featuresAdded = new FeaturesFile(false, file, protocol).parse(
361               viewport.getAlignment().getDataset(), fr.getFeatureColours(),
362               fr.getFeatureFilters(), false, relaxedIdMatching);
363     } catch (Exception ex)
364     {
365       ex.printStackTrace();
366     }
367
368     if (featuresAdded)
369     {
370       avcg.refreshFeatureUI(true);
371       if (fr != null)
372       {
373         // update the min/max ranges where necessary
374         fr.findAllFeatures(true);
375       }
376       if (avcg.getFeatureSettingsUI() != null)
377       {
378         avcg.getFeatureSettingsUI().discoverAllFeatureData();
379       }
380       alignPanel.paintAlignment(true, true);
381     }
382
383     return featuresAdded;
384
385   }
386
387   /**
388    * 
389    * Add highlighted sequences to selected rows. Exclude highlighted sequences
390    * from selected rows. toggle inclusion or exclusion of highlighted sequences.
391    * or add/exclude/toggle for sequences not highlighted.
392    * 
393    * @param invert
394    *          - when true, sequences that are not highlighted are used to modify
395    *          selection
396    * @param extendCurrent
397    *          - normally true , the current selected group is modified.
398    * @param toggle
399    *          - if a select
400    * @return
401    */
402   public boolean selectHighlightedSequences(boolean invert,
403           boolean extendCurrent, boolean toggle)
404   {
405     List<SequenceI> results = alignPanel.getAlignViewport()
406             .getHighlightedSeqs();
407
408     SequenceGroup sq = (extendCurrent
409             && viewport.getSelectionGroup() != null)
410                     ? viewport.getSelectionGroup()
411                     : new SequenceGroup();
412
413     if (invert)
414     {
415       List<SequenceI> nothighlighted = new ArrayList();
416       for (SequenceI seq : alignPanel.getAlignViewport().getAlignment()
417               .getSequences())
418       {
419         if (!results.contains(seq))
420         {
421           nothighlighted.add(seq);
422         }
423       }
424       results = nothighlighted;
425     }
426
427     if (results == null || results.size() == 0)
428     {
429       // do nothing if no selection exists
430       // unless toggle ??
431       return false;
432     }
433
434     boolean changed = false;
435
436     for (SequenceI seq : results)
437     {
438       int size = sq.getSize();
439       if (toggle)
440       {
441         sq.addOrRemove(seq, false);
442       }
443       else
444       {
445         sq.addSequence(seq, false);
446       }
447       changed |= size != sq.getSize();
448     }
449
450     if (sq.getSize() == 0)
451     {
452       viewport.setSelectionGroup(null);
453     }
454     else
455     {
456       if (sq != viewport.getSelectionGroup())
457     {
458       sq.setStartRes(0);
459       sq.setEndRes(viewport.getRanges().getAbsoluteAlignmentWidth());
460     }
461       viewport.setSelectionGroup(sq);
462     }
463
464     alignPanel.paintAlignment(false, false);
465
466     return changed;
467   }
468
469   @Override
470   public boolean markHighlightedColumns(boolean invert,
471           boolean extendCurrent, boolean toggle)
472   {
473     if (!viewport.hasSearchResults())
474     {
475       // do nothing if no selection exists
476       return false;
477     }
478     // JBPNote this routine could also mark rows, not just columns.
479     BitSet bs = new BitSet();
480     SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null
481             || extendCurrent) ? viewport.getAlignment()
482                     : viewport.getSelectionGroup();
483
484     // this could be a lambda... - the remains of the method is boilerplate,
485     // except for the different messages for reporting selection.
486     int nseq = viewport.getSearchResults().markColumns(sqcol, bs);
487
488     ColumnSelection cs = viewport.getColumnSelection();
489     if (cs == null)
490     {
491       cs = new ColumnSelection();
492     }
493
494     if (bs.cardinality() > 0 || invert)
495     {
496       boolean changed = cs.markColumns(bs, sqcol.getStartRes(),
497               sqcol.getEndRes(), invert, extendCurrent, toggle);
498       if (changed)
499       {
500         viewport.setColumnSelection(cs);
501         alignPanel.paintAlignment(false, false);
502         int columnCount = invert
503                 ? (sqcol.getEndRes() - sqcol.getStartRes() + 1)
504                         - bs.cardinality()
505                 : bs.cardinality();
506         avcg.setStatus(MessageManager.formatMessage(
507                 "label.view_controller_toggled_marked", new String[]
508                 { toggle ? MessageManager.getString("label.toggled")
509                         : MessageManager.getString("label.marked"),
510                     String.valueOf(columnCount),
511                     invert ? MessageManager
512                             .getString("label.not_containing")
513                             : MessageManager.getString("label.containing"),
514                     "Highlight", Integer.valueOf(nseq).toString() }));
515         return true;
516       }
517     }
518     else
519     {
520       avcg.setStatus(MessageManager
521               .formatMessage("No highlighted regions marked"));
522       if (!extendCurrent)
523       {
524         cs.clear();
525         alignPanel.paintAlignment(false, false);
526       }
527     }
528     return false;
529   }
530
531 }