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