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