1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (2.11.1.4)
3  * Copyright (C) 2021 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 jalview.util.MessageManager;
24 import jalview.ws.seqfetcher.DbSourceProxy;
25 
26 import java.awt.BorderLayout;
27 import java.awt.Component;
28 import java.awt.Dimension;
29 import java.awt.FlowLayout;
30 import java.awt.GridLayout;
31 import java.awt.event.ActionEvent;
32 import java.awt.event.ActionListener;
33 import java.awt.event.KeyEvent;
34 import java.awt.event.KeyListener;
35 import java.awt.event.MouseAdapter;
36 import java.awt.event.MouseEvent;
37 import java.util.ArrayList;
38 import java.util.HashSet;
39 import java.util.Hashtable;
40 import java.util.List;
41 import java.util.Vector;
42 
43 import javax.swing.JButton;
44 import javax.swing.JLabel;
45 import javax.swing.JPanel;
46 import javax.swing.JScrollPane;
47 import javax.swing.JTree;
48 import javax.swing.ToolTipManager;
49 import javax.swing.event.TreeSelectionEvent;
50 import javax.swing.event.TreeSelectionListener;
51 import javax.swing.tree.DefaultMutableTreeNode;
52 import javax.swing.tree.DefaultTreeCellRenderer;
53 import javax.swing.tree.DefaultTreeModel;
54 import javax.swing.tree.TreeCellRenderer;
55 import javax.swing.tree.TreeNode;
56 import javax.swing.tree.TreePath;
57 import javax.swing.tree.TreeSelectionModel;
58 
59 public class JDatabaseTree extends JalviewDialog implements KeyListener
60 {
61   boolean allowMultiSelections = false;
62 
63   public int action;
64 
getDatabaseSelectorButton()65   JButton getDatabaseSelectorButton()
66   {
67     final JButton viewdbs = new JButton(
68             MessageManager.getString("action.select_ddbb"));
69     viewdbs.addActionListener(new ActionListener()
70     {
71 
72       @Override
73       public void actionPerformed(ActionEvent arg0)
74       {
75         showDialog();
76       }
77     });
78     return viewdbs;
79   }
80 
81   JScrollPane svp;
82 
83   JTree dbviews;
84 
85   private jalview.ws.SequenceFetcher sfetcher;
86 
87   private JLabel dbstatus, dbstatex;
88 
89   private JPanel mainPanel = new JPanel(new BorderLayout());
90 
JDatabaseTree(jalview.ws.SequenceFetcher sfetch)91   public JDatabaseTree(jalview.ws.SequenceFetcher sfetch)
92   {
93     mainPanel.add(this);
94     initDialogFrame(mainPanel, true, false, MessageManager
95             .getString("label.select_database_retrieval_source"), 650, 490);
96     /*
97      * Dynamically generated database list will need a translation function from
98      * internal source to externally distinct names. UNIPROT and UP_NAME are
99      * identical DB sources, and should be collapsed.
100      */
101     DefaultMutableTreeNode tn = null, root = new DefaultMutableTreeNode();
102     Hashtable<String, DefaultMutableTreeNode> source = new Hashtable<>();
103     sfetcher = sfetch;
104     String dbs[] = sfetch.getSupportedDb();
105     Hashtable<String, String> ht = new Hashtable<>();
106     for (int i = 0; i < dbs.length; i++)
107     {
108       tn = source.get(dbs[i]);
109       List<DbSourceProxy> srcs = sfetch.getSourceProxy(dbs[i]);
110       if (tn == null)
111       {
112         source.put(dbs[i], tn = new DefaultMutableTreeNode(dbs[i], true));
113       }
114       for (DbSourceProxy dbp : srcs)
115       {
116         if (ht.get(dbp.getDbName()) == null)
117         {
118           tn.add(new DefaultMutableTreeNode(dbp, false));
119           ht.put(dbp.getDbName(), dbp.getDbName());
120         }
121         else
122         {
123           System.err.println("dupe ig for : " + dbs[i] + " \t"
124                   + dbp.getDbName() + " (" + dbp.getDbSource() + ")");
125           source.remove(tn);
126         }
127       }
128     }
129     for (int i = 0; i < dbs.length; i++)
130     {
131       tn = source.get(dbs[i]);
132       if (tn == null)
133       {
134         continue;
135       }
136       if (tn.getChildCount() == 1)
137       {
138         DefaultMutableTreeNode ttn = (DefaultMutableTreeNode) tn
139                 .getChildAt(0);
140         // remove nodes with only one child
141         tn.setUserObject(ttn.getUserObject());
142         tn.removeAllChildren();
143         source.put(dbs[i], tn);
144         tn.setAllowsChildren(false);
145       }
146       root.add(tn);
147     }
148     // and sort the tree
149     sortTreeNodes(root);
150     dbviews = new JTree(new DefaultTreeModel(root, false));
151     dbviews.setCellRenderer(new DbTreeRenderer(this));
152 
153     dbviews.getSelectionModel()
154             .setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
155     svp = new JScrollPane(dbviews);
156     svp.setMinimumSize(new Dimension(100, 200));
157     svp.setPreferredSize(new Dimension(200, 400));
158     svp.setMaximumSize(new Dimension(300, 600));
159 
160     JPanel panel = new JPanel(new BorderLayout());
161     panel.setSize(new Dimension(350, 220));
162     panel.add(svp);
163     dbviews.addTreeSelectionListener(new TreeSelectionListener()
164     {
165 
166       @Override
167       public void valueChanged(TreeSelectionEvent arg0)
168       {
169         _setSelectionState();
170       }
171     });
172     dbviews.addMouseListener(new MouseAdapter()
173     {
174 
175       @Override
176       public void mousePressed(MouseEvent e)
177       {
178         if (e.getClickCount() == 2)
179         {
180           okPressed();
181           closeDialog();
182         }
183       }
184     });
185     JPanel jc = new JPanel(new BorderLayout()),
186             j = new JPanel(new FlowLayout());
187     jc.add(svp, BorderLayout.CENTER);
188 
189     java.awt.Font f;
190     // TODO: make the panel stay a fixed size for longest dbname+example set.
191     JPanel dbstat = new JPanel(new GridLayout(2, 1));
192     dbstatus = new JLabel(" "); // set the height correctly for layout
193     dbstatus.setFont(f = JvSwingUtils.getLabelFont(false, true));
194     dbstatus.setSize(new Dimension(290, 50));
195     dbstatex = new JLabel(" ");
196     dbstatex.setFont(f);
197     dbstatex.setSize(new Dimension(290, 50));
198     dbstat.add(dbstatus);
199     dbstat.add(dbstatex);
200     jc.add(dbstat, BorderLayout.SOUTH);
201     jc.validate();
202     add(jc, BorderLayout.CENTER);
203     ok.setEnabled(false);
204     j.add(ok);
205     j.add(cancel);
206     add(j, BorderLayout.SOUTH);
207     dbviews.addKeyListener(this);
208     validate();
209   }
210 
sortTreeNodes(DefaultMutableTreeNode root)211   private void sortTreeNodes(DefaultMutableTreeNode root)
212   {
213     if (root.getChildCount() == 0)
214     {
215       return;
216     }
217     int count = root.getChildCount();
218     String[] names = new String[count];
219     DefaultMutableTreeNode[] nodes = new DefaultMutableTreeNode[count];
220     for (int i = 0; i < count; i++)
221     {
222       TreeNode node = root.getChildAt(i);
223       if (node instanceof DefaultMutableTreeNode)
224       {
225         DefaultMutableTreeNode child = (DefaultMutableTreeNode) node;
226         nodes[i] = child;
227         if (child.getUserObject() instanceof DbSourceProxy)
228         {
229           names[i] = ((DbSourceProxy) child.getUserObject()).getDbName()
230                   .toLowerCase();
231         }
232         else
233         {
234           names[i] = ((String) child.getUserObject()).toLowerCase();
235           sortTreeNodes(child);
236         }
237       }
238       else
239       {
240         throw new Error(MessageManager
241                 .getString("error.implementation_error_cant_reorder_tree"));
242       }
243     }
244     jalview.util.QuickSort.sort(names, nodes);
245     root.removeAllChildren();
246     for (int i = count - 1; i >= 0; i--)
247     {
248       root.add(nodes[i]);
249     }
250   }
251 
252   private class DbTreeRenderer extends DefaultTreeCellRenderer
253           implements TreeCellRenderer
254   {
255     JDatabaseTree us;
256 
DbTreeRenderer(JDatabaseTree me)257     public DbTreeRenderer(JDatabaseTree me)
258     {
259       us = me;
260       ToolTipManager.sharedInstance().registerComponent(dbviews);
261     }
262 
returnLabel(String txt)263     private Component returnLabel(String txt)
264     {
265       JLabel jl = new JLabel(txt);
266       jl.setFont(JvSwingUtils.getLabelFont());
267       return jl;
268     }
269 
270     @Override
getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)271     public Component getTreeCellRendererComponent(JTree tree, Object value,
272             boolean selected, boolean expanded, boolean leaf, int row,
273             boolean hasFocus)
274     {
275       String val = "";
276       if (value != null && value instanceof DefaultMutableTreeNode)
277       {
278         DefaultMutableTreeNode vl = (DefaultMutableTreeNode) value;
279         value = vl.getUserObject();
280         if (value instanceof DbSourceProxy)
281         {
282           val = ((DbSourceProxy) value).getDbName();
283           if (((DbSourceProxy) value).getDescription() != null)
284           { // getName()
285             this.setToolTipText(((DbSourceProxy) value).getDescription());
286           }
287         }
288         else
289         {
290           if (value instanceof String)
291           {
292             val = (String) value;
293           }
294         }
295       }
296       if (value == null)
297       {
298         val = "";
299       }
300       return super.getTreeCellRendererComponent(tree, val, selected,
301               expanded, leaf, row, hasFocus);
302 
303     }
304   }
305 
306   List<DbSourceProxy> oldselection, selection = null;
307 
308   TreePath[] tsel = null, oldtsel = null;
309 
310   @Override
raiseClosed()311   protected void raiseClosed()
312   {
313     for (ActionListener al : lstners)
314     {
315       al.actionPerformed(null);
316     }
317   }
318 
319   @Override
okPressed()320   protected void okPressed()
321   {
322     _setSelectionState();
323   }
324 
325   @Override
cancelPressed()326   protected void cancelPressed()
327   {
328     selection = oldselection;
329     tsel = oldtsel;
330     _revertSelectionState();
331     closeDialog();
332   }
333 
showDialog()334   void showDialog()
335   {
336     oldselection = selection;
337     oldtsel = tsel;
338     validate();
339     waitForInput();
340   }
341 
hasSelection()342   public boolean hasSelection()
343   {
344     return selection == null ? false : selection.size() == 0 ? false : true;
345   }
346 
getSelectedSources()347   public List<DbSourceProxy> getSelectedSources()
348   {
349     return selection;
350   }
351 
352   /**
353    * disable or enable selection handler
354    */
355   boolean handleSelections = true;
356 
_setSelectionState()357   private void _setSelectionState()
358   {
359     if (!handleSelections)
360     {
361       return;
362     }
363     ok.setEnabled(false);
364     if (dbviews.getSelectionCount() == 0)
365     {
366       selection = null;
367     }
368 
369     tsel = dbviews.getSelectionPaths();
370     boolean forcedFirstChild = false;
371     List<DbSourceProxy> srcs = new ArrayList<>();
372     if (tsel != null)
373     {
374       for (TreePath tp : tsel)
375       {
376         DefaultMutableTreeNode admt,
377                 dmt = (DefaultMutableTreeNode) tp.getLastPathComponent();
378         if (dmt.getUserObject() != null)
379         {
380           /*
381            * enable OK button once a selection has been made
382            */
383           ok.setEnabled(true);
384           if (dmt.getUserObject() instanceof DbSourceProxy)
385           {
386             srcs.add((DbSourceProxy) dmt.getUserObject());
387           }
388           else
389           {
390             if (allowMultiSelections)
391             {
392               srcs.addAll(sfetcher
393                       .getSourceProxy((String) dmt.getUserObject()));
394             }
395             else
396             {
397               srcs.add(sfetcher.getSourceProxy((String) dmt.getUserObject())
398                       .get(0));
399               forcedFirstChild = true;
400             }
401           }
402         }
403       }
404     }
405     updateDbStatus(srcs, forcedFirstChild);
406     selection = srcs;
407   }
408 
_revertSelectionState()409   private void _revertSelectionState()
410   {
411     handleSelections = false;
412     if (selection == null || selection.size() == 0)
413     {
414       dbviews.clearSelection();
415     }
416     else
417     {
418       dbviews.setSelectionPaths(tsel);
419     }
420     handleSelections = true;
421   }
422 
updateDbStatus(List<DbSourceProxy> srcs, boolean forcedFirstChild)423   private void updateDbStatus(List<DbSourceProxy> srcs,
424           boolean forcedFirstChild)
425   {
426     int x = 0;
427     String nm = "", qr = "";
428     for (DbSourceProxy dbs : srcs)
429     {
430       String tq = dbs.getTestQuery();
431       nm = dbs.getDbName();
432       if (tq != null && tq.trim().length() > 0 && dbs.isValidReference(tq))
433       {
434         qr = tq;
435         x++;
436       }
437     }
438 
439     dbstatex.setText(" ");
440     if (allowMultiSelections)
441     {
442       dbstatus.setText(MessageManager.formatMessage(
443               "label.selected_database_to_fetch_from", new String[]
444               { Integer.valueOf(srcs.size()).toString(),
445                   (srcs.size() == 1 ? "" : "s"),
446                   (srcs.size() > 0
447                           ? " with " + x + " test quer"
448                                   + (x == 1 ? "y" : "ies")
449                           : ".") }));
450     }
451     else
452     {
453       if (nm.length() > 0)
454       {
455         dbstatus.setText(MessageManager
456                 .formatMessage("label.database_param", new String[]
457                 { nm }));
458         if (qr.length() > 0)
459         {
460           dbstatex.setText(MessageManager
461                   .formatMessage("label.example_param", new String[]
462                   { qr }));
463         }
464       }
465       else
466       {
467         dbstatus.setText(" ");
468       }
469     }
470     dbstatus.invalidate();
471     dbstatex.invalidate();
472   }
473 
getSelectedItem()474   public String getSelectedItem()
475   {
476     if (hasSelection())
477     {
478       return getSelectedSources().get(0).getDbName();
479     }
480     return null;
481   }
482 
getExampleQueries()483   public String getExampleQueries()
484   {
485     if (!hasSelection())
486     {
487       return null;
488     }
489     StringBuffer sb = new StringBuffer();
490     HashSet<String> hs = new HashSet<>();
491     for (DbSourceProxy dbs : getSelectedSources())
492     {
493       String tq = dbs.getTestQuery();
494       ;
495       if (hs.add(tq))
496       {
497         if (sb.length() > 0)
498         {
499           sb.append(";");
500         }
501         sb.append(tq);
502       }
503     }
504     return sb.toString();
505   }
506 
507   List<ActionListener> lstners = new Vector<>();
508 
addActionListener(ActionListener actionListener)509   public void addActionListener(ActionListener actionListener)
510   {
511     lstners.add(actionListener);
512   }
513 
removeActionListener(ActionListener actionListener)514   public void removeActionListener(ActionListener actionListener)
515   {
516     lstners.remove(actionListener);
517   }
518 
519 
520   @Override
keyPressed(KeyEvent arg0)521   public void keyPressed(KeyEvent arg0)
522   {
523     if (!arg0.isConsumed() && arg0.getKeyCode() == KeyEvent.VK_ENTER)
524     {
525       action = arg0.getKeyCode();
526       okPressed();
527       closeDialog();
528     }
529     if (!arg0.isConsumed() && arg0.getKeyChar() == KeyEvent.VK_ESCAPE)
530     {
531       action = arg0.getKeyCode();
532       cancelPressed();
533     }
534   }
535 
536   @Override
keyReleased(KeyEvent arg0)537   public void keyReleased(KeyEvent arg0)
538   {
539     // TODO Auto-generated method stub
540 
541   }
542 
543   @Override
keyTyped(KeyEvent arg0)544   public void keyTyped(KeyEvent arg0)
545   {
546     // TODO Auto-generated method stub
547 
548   }
549 
550   @Override
setVisible(boolean arg0)551   public void setVisible(boolean arg0)
552   {
553     System.out.println("setVisible: " + arg0);
554     super.setVisible(arg0);
555   }
556 }
557