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