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