JAL-1563 Added fix to prevent swing timer from reinitiating search action when jalvie...
[jalview.git] / src / jalview / fts / core / GFTSPanel.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
22 package jalview.fts.core;
23
24 import jalview.fts.api.FTSDataColumnI;
25 import jalview.fts.api.GFTSPanelI;
26 import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
27 import jalview.gui.Desktop;
28 import jalview.gui.IProgressIndicator;
29 import jalview.gui.JvSwingUtils;
30 import jalview.gui.SequenceFetcher;
31 import jalview.util.MessageManager;
32
33 import java.awt.BorderLayout;
34 import java.awt.CardLayout;
35 import java.awt.Dimension;
36 import java.awt.event.ActionEvent;
37 import java.awt.event.ActionListener;
38 import java.awt.event.FocusEvent;
39 import java.awt.event.FocusListener;
40 import java.awt.event.KeyAdapter;
41 import java.awt.event.KeyEvent;
42 import java.awt.event.MouseAdapter;
43 import java.awt.event.MouseEvent;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collection;
47 import java.util.Collections;
48 import java.util.Comparator;
49 import java.util.List;
50
51 import javax.swing.ImageIcon;
52 import javax.swing.JButton;
53 import javax.swing.JComboBox;
54 import javax.swing.JFrame;
55 import javax.swing.JInternalFrame;
56 import javax.swing.JLabel;
57 import javax.swing.JPanel;
58 import javax.swing.JScrollPane;
59 import javax.swing.JTabbedPane;
60 import javax.swing.JTable;
61 import javax.swing.JTextField;
62 import javax.swing.Timer;
63 import javax.swing.event.ChangeEvent;
64 import javax.swing.event.ChangeListener;
65 import javax.swing.event.DocumentEvent;
66 import javax.swing.event.DocumentListener;
67 import javax.swing.table.DefaultTableModel;
68
69 /**
70  * This class provides the swing GUI layout for FTS Panel and implements most of
71  * the contracts defined in GFSPanelI
72  * 
73  * @author tcnofoegbu
74  *
75  */
76
77 @SuppressWarnings("serial")
78 public abstract class GFTSPanel extends JPanel implements GFTSPanelI
79 {
80   protected JInternalFrame mainFrame = new JInternalFrame(
81           getFTSFrameTitle());
82
83   protected IProgressIndicator progressIdicator;
84
85   protected JComboBox<FTSDataColumnI> cmb_searchTarget = new JComboBox<FTSDataColumnI>();
86
87   protected JButton btn_ok = new JButton();
88
89   protected JButton btn_back = new JButton();
90
91   protected JButton btn_cancel = new JButton();
92
93   protected JTextField txt_search = new JTextField(20);
94
95   protected SequenceFetcher seqFetcher;
96
97   protected Collection<FTSDataColumnI> wantedFields;
98
99   private String lastSearchTerm = "";
100
101   private JTable tbl_summary = new JTable()
102   {
103     @Override
104     public String getToolTipText(MouseEvent evt)
105     {
106       String toolTipText = null;
107       java.awt.Point pnt = evt.getPoint();
108       int rowIndex = rowAtPoint(pnt);
109       int colIndex = columnAtPoint(pnt);
110
111       try
112       {
113         if (getValueAt(rowIndex, colIndex) == null)
114         {
115           return null;
116         }
117         toolTipText = getValueAt(rowIndex, colIndex).toString();
118
119       } catch (Exception e)
120       {
121         e.printStackTrace();
122       }
123       toolTipText = (toolTipText == null ? null
124               : (toolTipText.length() > 500 ? JvSwingUtils.wrapTooltip(
125                       true, toolTipText.subSequence(0, 500) + "...")
126                       : JvSwingUtils.wrapTooltip(true, toolTipText)));
127
128       return toolTipText;
129     }
130   };
131
132   protected StringBuilder errorWarning = new StringBuilder();
133
134   protected JScrollPane scrl_searchResult = new JScrollPane(tbl_summary);
135
136   protected ImageIcon warningImage = new ImageIcon(getClass().getResource(
137           "/images/warning.gif"));
138
139   protected ImageIcon loadingImage = new ImageIcon(getClass().getResource(
140           "/images/loading.gif"));
141
142   protected JLabel lbl_warning = new JLabel(warningImage);
143
144   protected JLabel lbl_loading = new JLabel(loadingImage);
145
146   private JTabbedPane tabbedPane = new JTabbedPane();
147
148   private JPanel pnl_actions = new JPanel();
149
150   private JPanel pnl_results = new JPanel(new CardLayout());
151
152   private JPanel pnl_inputs = new JPanel();
153
154   private BorderLayout mainLayout = new BorderLayout();
155
156   protected Object[] previousWantedFields;
157
158   public GFTSPanel()
159   {
160     try
161     {
162       jbInit();
163       mainFrame.invalidate();
164       mainFrame.pack();
165     } catch (Exception e)
166     {
167       e.printStackTrace();
168     }
169   }
170
171   /**
172    * Initializes the GUI default properties
173    * 
174    * @throws Exception
175    */
176   private void jbInit() throws Exception
177   {
178     lbl_warning.setVisible(false);
179     lbl_warning.setFont(new java.awt.Font("Verdana", 0, 12));
180     lbl_loading.setVisible(false);
181     lbl_loading.setFont(new java.awt.Font("Verdana", 0, 12));
182
183     tbl_summary.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
184     tbl_summary.setAutoCreateRowSorter(true);
185     tbl_summary.getTableHeader().setReorderingAllowed(false);
186     tbl_summary.addMouseListener(new MouseAdapter()
187     {
188       @Override
189       public void mouseClicked(MouseEvent e)
190       {
191         validateSelection();
192       }
193
194       @Override
195       public void mouseReleased(MouseEvent e)
196       {
197         validateSelection();
198       }
199     });
200     tbl_summary.addKeyListener(new KeyAdapter()
201     {
202       @Override
203       public void keyPressed(KeyEvent evt)
204       {
205         validateSelection();
206         switch (evt.getKeyCode())
207         {
208         case KeyEvent.VK_ESCAPE: // escape key
209           btn_back_ActionPerformed();
210           break;
211         case KeyEvent.VK_ENTER: // enter key
212           if (btn_ok.isEnabled())
213           {
214             okAction();
215           }
216           evt.consume();
217           break;
218         case KeyEvent.VK_TAB: // tab key
219           if (evt.isShiftDown())
220           {
221             tabbedPane.requestFocus();
222           }
223           else
224           {
225             btn_back.requestFocus();
226           }
227           evt.consume();
228           break;
229         default:
230           return;
231         }
232       }
233     });
234
235     btn_back.setFont(new java.awt.Font("Verdana", 0, 12));
236     btn_back.setText(MessageManager.getString("action.back"));
237     btn_back.addActionListener(new java.awt.event.ActionListener()
238     {
239       @Override
240       public void actionPerformed(ActionEvent e)
241       {
242         btn_back_ActionPerformed();
243       }
244     });
245     btn_back.addKeyListener(new KeyAdapter()
246     {
247       @Override
248       public void keyPressed(KeyEvent evt)
249       {
250         if (evt.getKeyCode() == KeyEvent.VK_ENTER)
251         {
252           btn_back_ActionPerformed();
253         }
254       }
255     });
256
257     btn_ok.setEnabled(false);
258     btn_ok.setFont(new java.awt.Font("Verdana", 0, 12));
259     btn_ok.setText(MessageManager.getString("action.ok"));
260     btn_ok.addActionListener(new java.awt.event.ActionListener()
261     {
262       @Override
263       public void actionPerformed(ActionEvent e)
264       {
265         okAction();
266       }
267     });
268     btn_ok.addKeyListener(new KeyAdapter()
269     {
270       @Override
271       public void keyPressed(KeyEvent evt)
272       {
273         if (evt.getKeyCode() == KeyEvent.VK_ENTER)
274         {
275           okAction();
276         }
277       }
278     });
279
280     btn_cancel.setFont(new java.awt.Font("Verdana", 0, 12));
281     btn_cancel.setText(MessageManager.getString("action.cancel"));
282     btn_cancel.addActionListener(new java.awt.event.ActionListener()
283     {
284       @Override
285       public void actionPerformed(ActionEvent e)
286       {
287         btn_cancel_ActionPerformed();
288       }
289     });
290     btn_cancel.addKeyListener(new KeyAdapter()
291     {
292       @Override
293       public void keyPressed(KeyEvent evt)
294       {
295         if (evt.getKeyCode() == KeyEvent.VK_ENTER)
296         {
297           btn_cancel_ActionPerformed();
298         }
299       }
300     });
301
302     scrl_searchResult.setPreferredSize(new Dimension(500, 300));
303     scrl_searchResult
304             .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
305
306     cmb_searchTarget.setFont(new java.awt.Font("Verdana", 0, 12));
307     cmb_searchTarget.addActionListener(new ActionListener()
308     {
309       @Override
310       public void actionPerformed(ActionEvent e)
311       {
312         String tooltipText;
313         if ("all".equalsIgnoreCase(getCmbSearchTarget().getSelectedItem()
314                 .toString()))
315         {
316           tooltipText = MessageManager.getString("label.search_all");
317         }
318         else if ("pdb id".equalsIgnoreCase(getCmbSearchTarget()
319                 .getSelectedItem().toString()))
320         {
321           tooltipText = MessageManager
322                   .getString("label.separate_multiple_accession_ids");
323         }
324         else
325         {
326           tooltipText = MessageManager.formatMessage(
327                   "label.separate_multiple_query_values",
328                   new Object[] { getCmbSearchTarget().getSelectedItem()
329                           .toString() });
330         }
331         txt_search.setToolTipText(JvSwingUtils.wrapTooltip(true,
332                 tooltipText));
333         searchAction();
334       }
335     });
336
337     populateCmbSearchTargetOptions();
338
339     txt_search.setFont(new java.awt.Font("Verdana", 0, 12));
340
341     txt_search.addKeyListener(new KeyAdapter()
342     {
343       @Override
344       public void keyPressed(KeyEvent e)
345       {
346         if (e.getKeyCode() == KeyEvent.VK_ENTER)
347         {
348           if (txt_search.getText() == null
349                   || txt_search.getText().isEmpty())
350           {
351             return;
352           }
353           if ("pdb id".equalsIgnoreCase(getCmbSearchTarget()
354                   .getSelectedItem().toString()))
355           {
356             transferToSequenceFetcher(txt_search.getText());
357           }
358         }
359       }
360     });
361
362     final DeferredTextInputListener listener = new DeferredTextInputListener(
363             500,
364             new ActionListener()
365             {
366               @Override
367               public void actionPerformed(ActionEvent e)
368               {
369                 if (!getTypedText().equalsIgnoreCase(lastSearchTerm))
370                 {
371                   searchAction();
372                   lastSearchTerm = getTypedText();
373                 }
374               }
375             }, false);
376     txt_search.getDocument().addDocumentListener(listener);
377     txt_search.addFocusListener(new FocusListener()
378     {
379       @Override
380       public void focusGained(FocusEvent e)
381       {
382         listener.start();
383       }
384
385       @Override
386       public void focusLost(FocusEvent e)
387       {
388         listener.stop();
389       }
390     });
391
392     final String searchTabTitle = MessageManager
393             .getString("label.search_result");
394     final String configureCols = MessageManager
395             .getString("label.configure_displayed_columns");
396     ChangeListener changeListener = new ChangeListener()
397     {
398       @Override
399       public void stateChanged(ChangeEvent changeEvent)
400       {
401         JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent
402                 .getSource();
403         int index = sourceTabbedPane.getSelectedIndex();
404
405         btn_back.setVisible(true);
406         btn_cancel.setVisible(true);
407         btn_ok.setVisible(true);
408         if (sourceTabbedPane.getTitleAt(index).equals(configureCols))
409         {
410           btn_back.setVisible(false);
411           btn_cancel.setVisible(false);
412           btn_ok.setVisible(false);
413           btn_back.setEnabled(false);
414           btn_cancel.setEnabled(false);
415           btn_ok.setEnabled(false);
416           previousWantedFields = getFTSRestClient()
417                   .getAllDefaulDisplayedDataColumns()
418                   .toArray(new Object[0]);
419         }
420         if (sourceTabbedPane.getTitleAt(index).equals(searchTabTitle))
421         {
422           btn_back.setEnabled(true);
423           btn_cancel.setEnabled(true);
424           if (wantedFieldsUpdated())
425           {
426             searchAction();
427           }
428           else
429           {
430             validateSelection();
431           }
432         }
433       }
434     };
435     tabbedPane.addChangeListener(changeListener);
436     tabbedPane.setPreferredSize(new Dimension(500, 300));
437     tabbedPane.add(searchTabTitle, scrl_searchResult);
438     tabbedPane.add(configureCols, new FTSDataColumnPreferences(
439             PreferenceSource.SEARCH_SUMMARY, getFTSRestClient()));
440
441     pnl_actions.add(btn_back);
442     pnl_actions.add(btn_ok);
443     pnl_actions.add(btn_cancel);
444
445     pnl_results.add(tabbedPane);
446     pnl_inputs.add(cmb_searchTarget);
447     pnl_inputs.add(txt_search);
448     pnl_inputs.add(lbl_loading);
449     pnl_inputs.add(lbl_warning);
450
451     this.setLayout(mainLayout);
452     this.add(pnl_inputs, java.awt.BorderLayout.NORTH);
453     this.add(pnl_results, java.awt.BorderLayout.CENTER);
454     this.add(pnl_actions, java.awt.BorderLayout.SOUTH);
455     mainFrame.setVisible(true);
456     mainFrame.setContentPane(this);
457     mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
458     Desktop.addInternalFrame(mainFrame, getFTSFrameTitle(), 800, 400);
459   }
460
461   public class DeferredTextInputListener implements DocumentListener
462   {
463     private final Timer swingTimer;
464
465     public DeferredTextInputListener(int timeOut, ActionListener listener,
466             boolean repeats)
467     {
468       swingTimer = new Timer(timeOut, listener);
469       swingTimer.setRepeats(repeats);
470     }
471
472     public void start()
473     {
474       swingTimer.start();
475     }
476
477     public void stop()
478     {
479       swingTimer.stop();
480     }
481
482     @Override
483     public void insertUpdate(DocumentEvent e)
484     {
485       swingTimer.restart();
486     }
487
488     @Override
489     public void removeUpdate(DocumentEvent e)
490     {
491       swingTimer.restart();
492     }
493
494     @Override
495     public void changedUpdate(DocumentEvent e)
496     {
497       swingTimer.restart();
498     }
499
500   }
501
502   public boolean wantedFieldsUpdated()
503   {
504     if (previousWantedFields == null)
505     {
506       return true;
507     }
508
509     return Arrays.equals(getFTSRestClient()
510             .getAllDefaulDisplayedDataColumns()
511             .toArray(new Object[0]), previousWantedFields) ? false
512             : true;
513
514   }
515
516   public void validateSelection()
517   {
518     if (tbl_summary.getSelectedRows().length > 0)
519     {
520       btn_ok.setEnabled(true);
521     }
522     else
523     {
524       btn_ok.setEnabled(false);
525     }
526   }
527
528   public JComboBox<FTSDataColumnI> getCmbSearchTarget()
529   {
530     return cmb_searchTarget;
531   }
532
533   public JTextField getTxtSearch()
534   {
535     return txt_search;
536   }
537
538   public JInternalFrame getMainFrame()
539   {
540     return mainFrame;
541   }
542
543   protected void delayAndEnableActionButtons()
544   {
545     new Thread()
546     {
547       @Override
548       public void run()
549       {
550         try
551         {
552           Thread.sleep(1500);
553         } catch (InterruptedException e)
554         {
555           e.printStackTrace();
556         }
557         btn_ok.setEnabled(true);
558         btn_back.setEnabled(true);
559         btn_cancel.setEnabled(true);
560       }
561     }.start();
562   }
563
564   protected void checkForErrors()
565   {
566     lbl_warning.setVisible(false);
567     if (errorWarning.length() > 0)
568     {
569       lbl_loading.setVisible(false);
570       lbl_warning.setToolTipText(JvSwingUtils.wrapTooltip(true,
571               errorWarning.toString()));
572       lbl_warning.setVisible(true);
573     }
574   }
575
576   protected void btn_back_ActionPerformed()
577   {
578     mainFrame.dispose();
579     new SequenceFetcher(progressIdicator);
580   }
581
582   protected void disableActionButtons()
583   {
584     btn_ok.setEnabled(false);
585     btn_back.setEnabled(false);
586     btn_cancel.setEnabled(false);
587   }
588
589   protected void btn_cancel_ActionPerformed()
590   {
591     mainFrame.dispose();
592   }
593
594   /**
595    * Populates search target combo-box options
596    */
597   public void populateCmbSearchTargetOptions()
598   {
599     List<FTSDataColumnI> searchableTargets = new ArrayList<FTSDataColumnI>();
600     try
601     {
602       Collection<FTSDataColumnI> foundFTSTargets = getFTSRestClient()
603               .getSearchableDataColumns();
604       searchableTargets.addAll(foundFTSTargets);
605     } catch (Exception e)
606     {
607       e.printStackTrace();
608     }
609
610     Collections.sort(searchableTargets, new Comparator<FTSDataColumnI>()
611     {
612       @Override
613       public int compare(FTSDataColumnI o1, FTSDataColumnI o2)
614       {
615         return o1.getName().compareTo(o2.getName());
616       }
617     });
618
619     for (FTSDataColumnI searchTarget : searchableTargets)
620     {
621       cmb_searchTarget.addItem(searchTarget);
622     }
623   }
624
625
626   public void transferToSequenceFetcher(String ids)
627   {
628     // mainFrame.dispose();
629     seqFetcher.getTextArea().setText(ids);
630     Thread worker = new Thread(seqFetcher);
631     worker.start();
632   }
633
634   @Override
635   public String getTypedText()
636   {
637     return txt_search.getText().trim();
638   }
639
640   @Override
641   public JTable getResultTable()
642   {
643     return tbl_summary;
644   }
645
646   public void reset()
647   {
648     lbl_loading.setVisible(false);
649     errorWarning.setLength(0);
650     lbl_warning.setVisible(false);
651     btn_ok.setEnabled(false);
652     mainFrame.setTitle(getFTSFrameTitle());
653     referesh();
654     tbl_summary.setModel(new DefaultTableModel());
655     tbl_summary.setVisible(false);
656   }
657
658   @Override
659   public void setErrorMessage(String message)
660   {
661     errorWarning.append(message);
662   }
663
664   @Override
665   public void updateSearchFrameTitle(String title)
666   {
667     mainFrame.setTitle(title);
668   }
669
670   @Override
671   public void setSearchInProgress(Boolean isSearchInProgress)
672   {
673     lbl_loading.setVisible(isSearchInProgress);
674   }
675   public void referesh()
676   {
677     mainFrame.setTitle(getFTSFrameTitle());
678   }
679
680 }