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