Clover icon

Coverage Report

  1. Project Clover database Wed Nov 13 2024 16:21:17 GMT
  2. Package jalview.gui

File Finder.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
59% of files have more coverage

Code metrics

42
107
14
1
427
274
43
0.4
7.64
14
3.07

Classes

Class Line # Actions
Finder 64 107 43
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

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.gui;
22   
23    import java.awt.Dimension;
24    import java.awt.event.ActionEvent;
25    import java.awt.event.FocusAdapter;
26    import java.awt.event.FocusEvent;
27    import java.awt.event.KeyEvent;
28    import java.util.ArrayList;
29    import java.util.HashMap;
30    import java.util.List;
31    import java.util.Locale;
32    import java.util.Map;
33    import java.util.regex.Pattern;
34    import java.util.regex.PatternSyntaxException;
35   
36    import javax.swing.AbstractAction;
37    import javax.swing.JComponent;
38    import javax.swing.JInternalFrame;
39    import javax.swing.JLayeredPane;
40    import javax.swing.KeyStroke;
41    import javax.swing.event.InternalFrameAdapter;
42    import javax.swing.event.InternalFrameEvent;
43   
44    import jalview.api.AlignViewportI;
45    import jalview.api.FinderI;
46    import jalview.datamodel.SearchResultMatchI;
47    import jalview.datamodel.SearchResultsI;
48    import jalview.datamodel.SequenceFeature;
49    import jalview.datamodel.SequenceI;
50    import jalview.jbgui.GFinder;
51    import jalview.util.MessageManager;
52   
53    /**
54    * Performs the menu option for searching the alignment, for the next or all
55    * matches. If matches are found, they are highlighted, and the user has the
56    * option to create a new feature on the alignment for the matched positions.
57    *
58    * Searches can be for a simple base sequence, or may use a regular expression.
59    * Any gaps are ignored.
60    *
61    * @author $author$
62    * @version $Revision$
63    */
 
64    public class Finder extends GFinder
65    {
66    private static final int MIN_WIDTH = 350;
67   
68    private static final int MIN_HEIGHT = 120;
69   
70    private static final int MY_HEIGHT = 150;
71   
72    private static final int MY_WIDTH = 400;
73   
74    private AlignViewportI av;
75   
76    private AlignmentPanel ap;
77   
78    private JInternalFrame frame;
79   
80    /*
81    * Finder agent per viewport searched
82    */
83    private Map<AlignViewportI, FinderI> finders;
84   
85    private SearchResultsI searchResults;
86   
87    /*
88    * true if Finder always acts on the same alignment,
89    * false if it acts on the alignment with focus
90    */
91    private boolean focusFixed;
92   
93    /**
94    * Constructor given an associated alignment panel. Constructs and displays an
95    * internal frame where the user can enter a search string. The Finder may
96    * have 'fixed focus' (always act the panel for which it is constructed), or
97    * not (acts on the alignment that has focus). An optional 'scope' may be
98    * added to be shown in the title of the Finder frame.
99    *
100    * @param alignPanel
101    * @param fixedFocus
102    * @param scope
103    */
 
104  0 toggle public Finder(AlignmentPanel alignPanel, boolean fixedFocus, String scope)
105    {
106  0 av = alignPanel.getAlignViewport();
107  0 ap = alignPanel;
108  0 focusFixed = fixedFocus;
109  0 finders = new HashMap<>();
110  0 frame = new JInternalFrame();
111  0 frame.setFrameIcon(null);
112  0 frame.setContentPane(this);
113  0 frame.setLayer(JLayeredPane.PALETTE_LAYER);
114  0 frame.addInternalFrameListener(new InternalFrameAdapter()
115    {
 
116  0 toggle @Override
117    public void internalFrameClosing(InternalFrameEvent e)
118    {
119  0 closeAction();
120    }
121    });
122  0 frame.addFocusListener(new FocusAdapter()
123    {
 
124  0 toggle @Override
125    public void focusGained(FocusEvent e)
126    {
127    /*
128    * ensure 'ignore hidden columns' is only enabled
129    * if the alignment with focus has hidden columns
130    */
131  0 getFocusedViewport();
132    }
133    });
134   
135  0 addEscapeHandler();
136   
137  0 String title = MessageManager.getString("label.find");
138  0 if (scope != null)
139    {
140  0 title += " " + scope;
141    }
142  0 Desktop.addInternalFrame(frame, title, MY_WIDTH, MY_HEIGHT);
143  0 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
144  0 searchBox.getComponent().requestFocus();
145    }
146   
147    /**
148    * Add a handler for the Escape key when the window has focus
149    */
 
150  0 toggle private void addEscapeHandler()
151    {
152  0 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
153    .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Cancel");
154  0 getRootPane().getActionMap().put("Cancel", new AbstractAction()
155    {
 
156  0 toggle @Override
157    public void actionPerformed(ActionEvent e)
158    {
159  0 closeAction();
160    }
161    });
162    }
163   
164    /**
165    * Performs the 'Find Next' action on the alignment panel with focus
166    */
 
167  0 toggle @Override
168    public void findNext_actionPerformed()
169    {
170  0 if (getFocusedViewport())
171    {
172  0 doSearch(false);
173    }
174    }
175   
176    /**
177    * Performs the 'Find All' action on the alignment panel with focus
178    */
 
179  0 toggle @Override
180    public void findAll_actionPerformed()
181    {
182  0 if (getFocusedViewport())
183    {
184  0 doSearch(true);
185    }
186    }
187   
188    /**
189    * if !focusfixed and not in a desktop environment, checks that av and ap are
190    * valid. Otherwise, gets the topmost alignment window and sets av and ap
191    * accordingly. Also sets the 'ignore hidden' checkbox disabled if the
192    * viewport has no hidden columns.
193    *
194    * @return false if no alignment window was found
195    */
 
196  0 toggle boolean getFocusedViewport()
197    {
198  0 if (focusFixed || Desktop.desktop == null)
199    {
200  0 if (ap != null && av != null)
201    {
202  0 ignoreHidden.setEnabled(av.hasHiddenColumns());
203  0 return true;
204    }
205    // we aren't in a desktop environment, so give up now.
206  0 return false;
207    }
208    // now checks further down the window stack to fix bug
209    // https://mantis.lifesci.dundee.ac.uk/view.php?id=36008
210  0 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
211  0 for (int f = 0; f < frames.length; f++)
212    {
213  0 JInternalFrame alignFrame = frames[f];
214  0 if (alignFrame != null && alignFrame instanceof AlignFrame
215    && !alignFrame.isIcon())
216    {
217  0 av = ((AlignFrame) alignFrame).viewport;
218  0 ap = ((AlignFrame) alignFrame).alignPanel;
219  0 ignoreHidden.setEnabled(av.hasHiddenColumns());
220  0 return true;
221    }
222    }
223  0 return false;
224    }
225   
226    /**
227    * Opens a dialog that allows the user to create sequence features for the
228    * find match results
229    */
 
230  0 toggle @Override
231    public void createFeatures_actionPerformed()
232    {
233  0 if (searchResults.isEmpty())
234    {
235  0 return; // shouldn't happen
236    }
237  0 List<SequenceI> seqs = new ArrayList<>();
238  0 List<SequenceFeature> features = new ArrayList<>();
239   
240  0 String searchString = searchBox.getUserInput();
241  0 String desc = "Search Results";
242   
243    /*
244    * assemble dataset sequences, and template new sequence features,
245    * for the amend features dialog
246    */
247  0 for (SearchResultMatchI match : searchResults.getResults())
248    {
249  0 seqs.add(match.getSequence().getDatasetSequence());
250  0 features.add(new SequenceFeature(searchString, desc, match.getStart(),
251    match.getEnd(), desc));
252    }
253   
254  0 new FeatureEditor(ap, seqs, features, true).showDialog();
255    }
256   
 
257  0 toggle @Override
258    protected void copyToClipboard_actionPerformed()
259    {
260  0 if (searchResults.isEmpty())
261    {
262  0 return; // shouldn't happen
263    }
264    // assume viewport controller has same searchResults as we do...
265  0 ap.alignFrame.avc.copyHighlightedRegionsToClipboard();
266    }
267   
268    /**
269    * Search the alignment for the next or all matches. If 'all matches', a
270    * dialog is shown with the number of sequence ids and subsequences matched.
271    *
272    * @param doFindAll
273    */
 
274  0 toggle void doSearch(boolean doFindAll)
275    {
276  0 createFeatures.setEnabled(false);
277  0 copyToClipboard.setEnabled(false);
278   
279  0 String searchString = searchBox.getUserInput();
280   
281  0 if (isInvalidSearchString(searchString))
282    {
283  0 return;
284    }
285    // TODO: extend finder to match descriptions, features and annotation, and
286    // other stuff
287    // TODO: add switches to control what is searched - sequences, IDS,
288    // descriptions, features
289  0 FinderI finder = finders.get(av);
290  0 if (finder == null)
291    {
292    /*
293    * first time we've searched this viewport
294    */
295  0 finder = new jalview.analysis.Finder(av);
296  0 finders.put(av, finder);
297    }
298  0 finder.setFeatureRenderer(ap.getFeatureRenderer());
299   
300  0 boolean isCaseSensitive = caseSensitive.isSelected();
301  0 boolean doSearchDescription = searchDescription.isSelected();
302  0 boolean doSearchfeatures = searchFeatures.isSelected();
303  0 boolean skipHidden = ignoreHidden.isSelected();
304  0 if (doFindAll)
305    {
306  0 finder.findAll(searchString, isCaseSensitive, doSearchDescription,
307    doSearchfeatures, skipHidden);
308    }
309    else
310    {
311  0 finder.findNext(searchString, isCaseSensitive, doSearchDescription,
312    doSearchfeatures, skipHidden);
313    }
314   
315  0 searchResults = finder.getSearchResults();
316  0 List<SequenceI> idMatch = finder.getIdMatches();
317  0 ap.getIdPanel().highlightSearchResults(idMatch);
318   
319  0 if (searchResults.isEmpty())
320    {
321  0 searchResults = null;
322    }
323    else
324    {
325  0 createFeatures.setEnabled(true);
326  0 copyToClipboard.setEnabled(true);
327    }
328   
329  0 searchBox.updateCache();
330   
331  0 ap.highlightSearchResults(searchResults);
332    // TODO: add enablers for 'SelectSequences' or 'SelectColumns' or
333    // 'SelectRegion' selection
334  0 if (idMatch.isEmpty() && searchResults == null)
335    {
336  0 JvOptionPane.showInternalMessageDialog(this,
337    MessageManager.getString("label.finished_searching"), null,
338    JvOptionPane.PLAIN_MESSAGE);
339    }
340    else
341    {
342  0 if (doFindAll)
343    {
344    // then we report the matches that were found
345  0 StringBuilder message = new StringBuilder();
346  0 if (idMatch.size() > 0)
347    {
348  0 message.append(idMatch.size()).append(" IDs");
349    }
350  0 if (searchResults != null)
351    {
352  0 if (idMatch.size() > 0 && searchResults.getCount() > 0)
353    {
354  0 message.append(" ").append(MessageManager.getString("label.and")
355    .toLowerCase(Locale.ROOT)).append(" ");
356    }
357  0 message.append(MessageManager.formatMessage(
358    "label.subsequence_matches_found",
359    searchResults.getCount()));
360    }
361  0 JvOptionPane.showInternalMessageDialog(this, message.toString(),
362    null, JvOptionPane.INFORMATION_MESSAGE);
363    }
364    }
365    }
366   
367    /**
368    * Displays an error dialog, and answers false, if the search string is
369    * invalid, else answers true.
370    *
371    * @param searchString
372    * @return
373    */
 
374  0 toggle protected boolean isInvalidSearchString(String searchString)
375    {
376  0 String error = getSearchValidationError(searchString);
377  0 if (error == null)
378    {
379  0 return false;
380    }
381  0 JvOptionPane.showInternalMessageDialog(this, error,
382    MessageManager.getString("label.invalid_search"), // $NON-NLS-1$
383    JvOptionPane.ERROR_MESSAGE);
384  0 return true;
385    }
386   
387    /**
388    * Returns an error message string if the search string is invalid, else
389    * returns null.
390    *
391    * Currently validation is limited to checking the string is not empty, and is
392    * a valid regular expression (simple searches for base sub-sequences will
393    * pass this test). Additional validations may be added in future if the
394    * search syntax is expanded.
395    *
396    * @param searchString
397    * @return
398    */
 
399  0 toggle protected String getSearchValidationError(String searchString)
400    {
401  0 String error = null;
402  0 if (searchString == null || searchString.length() == 0)
403    {
404  0 error = MessageManager.getString("label.invalid_search");
405    }
406  0 try
407    {
408  0 Pattern.compile(searchString);
409    } catch (PatternSyntaxException e)
410    {
411  0 error = MessageManager.getString("error.invalid_regex") + ": "
412    + e.getDescription();
413    }
414  0 return error;
415    }
416   
 
417  0 toggle protected void closeAction()
418    {
419  0 frame.setVisible(false);
420  0 frame.dispose();
421  0 searchBox.persistCache();
422  0 if (getFocusedViewport())
423    {
424  0 ap.alignFrame.requestFocus();
425    }
426    }
427    }