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