2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
22 package jalview.jbgui;
24 import jalview.datamodel.SequenceI;
25 import jalview.fts.api.FTSDataColumnI;
26 import jalview.fts.core.FTSDataColumnPreferences;
27 import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
28 import jalview.fts.service.pdb.PDBFTSRestClient;
29 import jalview.gui.AlignmentPanel;
30 import jalview.gui.Desktop;
31 import jalview.gui.JvSwingUtils;
32 import jalview.gui.StructureViewer;
33 import jalview.util.MessageManager;
35 import java.awt.BorderLayout;
36 import java.awt.CardLayout;
37 import java.awt.Component;
38 import java.awt.Dimension;
39 import java.awt.FlowLayout;
41 import java.awt.GridLayout;
42 import java.awt.event.ActionEvent;
43 import java.awt.event.ActionListener;
44 import java.awt.event.ItemEvent;
45 import java.awt.event.ItemListener;
46 import java.awt.event.KeyAdapter;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.util.Arrays;
51 import java.util.HashMap;
54 import javax.swing.ImageIcon;
55 import javax.swing.JButton;
56 import javax.swing.JCheckBox;
57 import javax.swing.JComboBox;
58 import javax.swing.JFrame;
59 import javax.swing.JInternalFrame;
60 import javax.swing.JLabel;
61 import javax.swing.JList;
62 import javax.swing.JPanel;
63 import javax.swing.JScrollPane;
64 import javax.swing.JSeparator;
65 import javax.swing.JTabbedPane;
66 import javax.swing.JTable;
67 import javax.swing.JTextField;
68 import javax.swing.ListCellRenderer;
69 import javax.swing.Timer;
70 import javax.swing.event.ChangeEvent;
71 import javax.swing.event.ChangeListener;
72 import javax.swing.event.DocumentEvent;
73 import javax.swing.event.DocumentListener;
74 import javax.swing.event.InternalFrameEvent;
75 import javax.swing.table.TableColumn;
77 import net.miginfocom.swing.MigLayout;
79 @SuppressWarnings("serial")
81 * GUI layout for structure chooser
86 public abstract class GStructureChooser extends JPanel
87 implements ItemListener
89 private static final Font VERDANA_12 = new Font("Verdana", 0, 12);
91 protected static final String VIEWS_FILTER = "VIEWS_FILTER";
93 protected static final String VIEWS_FROM_FILE = "VIEWS_FROM_FILE";
95 protected static final String VIEWS_ENTER_ID = "VIEWS_ENTER_ID";
98 * 'cached' structure view
100 protected static final String VIEWS_LOCAL_PDB = "VIEWS_LOCAL_PDB";
102 protected JPanel statusPanel = new JPanel();
104 public JLabel statusBar = new JLabel();
106 protected String frameTitle = MessageManager
107 .getString("label.structure_chooser");
109 protected JInternalFrame mainFrame = new JInternalFrame(frameTitle);
111 protected JComboBox<FilterOption> cmb_filterOption = new JComboBox<>();
113 protected AlignmentPanel ap;
115 protected StringBuilder errorWarning = new StringBuilder();
117 protected JButton btn_add;
119 protected JButton btn_newView;
121 protected JButton btn_pdbFromFile = new JButton();
123 protected JCheckBox chk_superpose = new JCheckBox(
124 MessageManager.getString("label.superpose_structures"));
126 protected JTextField txt_search = new JTextField(14);
128 protected JPanel pnl_switchableViews = new JPanel(new CardLayout());
130 protected CardLayout layout_switchableViews = (CardLayout) (pnl_switchableViews
133 protected JCheckBox chk_invertFilter = new JCheckBox(
134 MessageManager.getString("label.invert"));
136 protected ImageIcon loadingImage = new ImageIcon(
137 getClass().getResource("/images/loading.gif"));
139 protected ImageIcon goodImage = new ImageIcon(
140 getClass().getResource("/images/good.png"));
142 protected ImageIcon errorImage = new ImageIcon(
143 getClass().getResource("/images/error.png"));
145 protected ImageIcon warningImage = new ImageIcon(
146 getClass().getResource("/images/warning.gif"));
148 protected JLabel lbl_loading = new JLabel(loadingImage);
150 protected JLabel lbl_pdbManualFetchStatus = new JLabel(errorImage);
152 protected JLabel lbl_fromFileStatus = new JLabel(errorImage);
154 protected AssociateSeqPanel idInputAssSeqPanel = new AssociateSeqPanel();
156 protected AssociateSeqPanel fileChooserAssSeqPanel = new AssociateSeqPanel();
158 protected JComboBox<StructureViewer> targetView = new JComboBox<>();
160 protected JTable tbl_local_pdb = new JTable();
162 protected JTabbedPane pnl_filter = new JTabbedPane();
164 protected abstract FTSDataColumnPreferences getFTSDocFieldPrefs();
165 protected abstract void setFTSDocFieldPrefs(FTSDataColumnPreferences newPrefs);
168 protected FTSDataColumnI[] previousWantedFields;
170 protected static Map<String, Integer> tempUserPrefs = new HashMap<>();
172 private JTable tbl_summary = new JTable()
174 private boolean inLayout;
177 public boolean getScrollableTracksViewportWidth()
179 return hasExcessWidth();
184 public void doLayout()
186 if (hasExcessWidth())
188 autoResizeMode = AUTO_RESIZE_SUBSEQUENT_COLUMNS;
193 autoResizeMode = AUTO_RESIZE_OFF;
196 protected boolean hasExcessWidth()
198 return getPreferredSize().width < getParent().getWidth();
202 public void columnMarginChanged(ChangeEvent e)
208 TableColumn resizingColumn = getTableHeader().getResizingColumn();
209 // Need to do this here, before the parent's
210 // layout manager calls getPreferredSize().
211 if (resizingColumn != null && autoResizeMode == AUTO_RESIZE_OFF
214 resizingColumn.setPreferredWidth(resizingColumn.getWidth());
215 String colHeader = resizingColumn.getHeaderValue().toString();
216 tempUserPrefs.put(colHeader, resizingColumn.getWidth());
222 public String getToolTipText(MouseEvent evt)
224 String toolTipText = null;
225 java.awt.Point pnt = evt.getPoint();
226 int rowIndex = rowAtPoint(pnt);
227 int colIndex = columnAtPoint(pnt);
231 if (getValueAt(rowIndex, colIndex) == null)
235 toolTipText = getValueAt(rowIndex, colIndex).toString();
236 } catch (Exception e)
238 // e.printStackTrace();
240 toolTipText = (toolTipText == null ? null
241 : (toolTipText.length() > 500
242 ? JvSwingUtils.wrapTooltip(true,
243 "\"" + toolTipText.subSequence(0, 500)
245 : JvSwingUtils.wrapTooltip(true, toolTipText)));
250 public GStructureChooser()
253 protected void initDialog()
259 mainFrame.setVisible(false);
260 mainFrame.invalidate();
262 } catch (Exception e)
264 System.out.println(e); // for JavaScript TypeError
269 // BH SwingJS optimization
270 // (a) 100-ms interruptable timer for text entry -- BH 1/10/2019
271 // (b) two-character minimum, at least for JavaScript.
275 protected void txt_search_ActionPerformedDelayed() {
279 timer = new Timer(300, new ActionListener() {
282 public void actionPerformed(ActionEvent e) {
283 txt_search_ActionPerformed();
286 timer.setRepeats(false);
292 * Initializes the GUI default properties
296 private void jbInit() throws Exception
298 Integer width = tempUserPrefs.get("structureChooser.width") == null
300 : tempUserPrefs.get("structureChooser.width");
301 Integer height = tempUserPrefs.get("structureChooser.height") == null
303 : tempUserPrefs.get("structureChooser.height");
304 tbl_summary.setAutoCreateRowSorter(true);
305 tbl_summary.getTableHeader().setReorderingAllowed(false);
306 tbl_summary.addMouseListener(new MouseAdapter()
309 public void mouseClicked(MouseEvent e)
311 validateSelections();
315 public void mouseReleased(MouseEvent e)
317 validateSelections();
320 tbl_summary.addKeyListener(new KeyAdapter()
323 public void keyPressed(KeyEvent evt)
325 validateSelections();
326 switch (evt.getKeyCode())
328 case KeyEvent.VK_ESCAPE: // escape key
331 case KeyEvent.VK_ENTER: // enter key
332 if (btn_add.isEnabled())
334 add_ActionPerformed();
337 case KeyEvent.VK_TAB: // tab key
338 if (evt.isShiftDown())
340 pnl_filter.requestFocus();
344 btn_add.requestFocus();
354 JButton btn_cancel = new JButton(
355 MessageManager.getString("action.cancel"));
356 btn_cancel.setFont(VERDANA_12);
357 btn_cancel.addActionListener(new java.awt.event.ActionListener()
360 public void actionPerformed(ActionEvent e)
362 closeAction(pnl_filter.getHeight());
365 btn_cancel.addKeyListener(new KeyAdapter()
368 public void keyPressed(KeyEvent evt)
370 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
372 closeAction(pnl_filter.getHeight());
377 tbl_local_pdb.setAutoCreateRowSorter(true);
378 tbl_local_pdb.getTableHeader().setReorderingAllowed(false);
379 tbl_local_pdb.addMouseListener(new MouseAdapter()
382 public void mouseClicked(MouseEvent e)
384 validateSelections();
388 public void mouseReleased(MouseEvent e)
390 validateSelections();
393 tbl_local_pdb.addKeyListener(new KeyAdapter()
396 public void keyPressed(KeyEvent evt)
398 validateSelections();
399 switch (evt.getKeyCode())
401 case KeyEvent.VK_ESCAPE: // escape key
404 case KeyEvent.VK_ENTER: // enter key
405 if (btn_add.isEnabled())
407 add_ActionPerformed();
410 case KeyEvent.VK_TAB: // tab key
411 if (evt.isShiftDown())
413 cmb_filterOption.requestFocus();
417 if (btn_add.isEnabled())
419 btn_add.requestFocus();
423 btn_cancel.requestFocus();
434 btn_newView = new JButton(MessageManager.getString("action.new_view"));
435 btn_newView.setFont(VERDANA_12);
436 btn_newView.addActionListener(new java.awt.event.ActionListener()
439 public void actionPerformed(ActionEvent e)
441 newView_ActionPerformed();
444 btn_newView.addKeyListener(new KeyAdapter()
447 public void keyPressed(KeyEvent evt)
449 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
451 newView_ActionPerformed();
456 btn_add = new JButton(MessageManager.getString("action.add"));
457 btn_add.setFont(VERDANA_12);
458 btn_add.addActionListener(new java.awt.event.ActionListener()
461 public void actionPerformed(ActionEvent e)
463 add_ActionPerformed();
466 btn_add.addKeyListener(new KeyAdapter()
469 public void keyPressed(KeyEvent evt)
471 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
473 add_ActionPerformed();
478 btn_pdbFromFile.setFont(VERDANA_12);
479 String btn_title = MessageManager.getString("label.select_pdb_file");
480 btn_pdbFromFile.setText(btn_title + " ");
481 btn_pdbFromFile.addActionListener(new java.awt.event.ActionListener()
484 public void actionPerformed(ActionEvent e)
486 pdbFromFile_actionPerformed();
489 btn_pdbFromFile.addKeyListener(new KeyAdapter()
492 public void keyPressed(KeyEvent evt)
494 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
496 pdbFromFile_actionPerformed();
501 JScrollPane scrl_foundStructures = new JScrollPane(tbl_summary);
502 scrl_foundStructures.setPreferredSize(new Dimension(width, height));
504 JScrollPane scrl_localPDB = new JScrollPane(tbl_local_pdb);
505 scrl_localPDB.setPreferredSize(new Dimension(width, height));
506 scrl_localPDB.setHorizontalScrollBarPolicy(
507 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
509 chk_invertFilter.setFont(VERDANA_12);
510 txt_search.setToolTipText(JvSwingUtils.wrapTooltip(true,
511 MessageManager.getString("label.enter_pdb_id_tip")));
512 txt_search.getDocument().addDocumentListener(new DocumentListener()
515 public void insertUpdate(DocumentEvent e)
517 txt_search_ActionPerformedDelayed();
521 public void removeUpdate(DocumentEvent e)
523 txt_search_ActionPerformedDelayed();
527 public void changedUpdate(DocumentEvent e)
529 txt_search_ActionPerformedDelayed();
533 cmb_filterOption.setFont(VERDANA_12);
534 cmb_filterOption.setToolTipText(
535 MessageManager.getString("info.select_filter_option"));
536 cmb_filterOption.addItemListener(this);
537 // add CustomComboSeparatorsRenderer to filter option combo-box
538 cmb_filterOption.setRenderer(new CustomComboSeparatorsRenderer(
539 (ListCellRenderer<Object>) cmb_filterOption.getRenderer())
542 protected boolean addSeparatorAfter(JList list, FilterOption value,
545 return value.isAddSeparatorAfter();
549 chk_invertFilter.addItemListener(this);
551 targetView.setVisible(false);
553 JPanel actionsPanel = new JPanel(new MigLayout());
554 actionsPanel.add(targetView, "left");
555 actionsPanel.add(btn_add, "wrap");
556 actionsPanel.add(chk_superpose, "left");
557 actionsPanel.add(btn_newView);
558 actionsPanel.add(btn_cancel, "right");
560 JPanel pnl_main = new JPanel();
561 pnl_main.add(cmb_filterOption);
562 pnl_main.add(lbl_loading);
563 pnl_main.add(chk_invertFilter);
564 lbl_loading.setVisible(false);
566 JPanel pnl_fileChooser = new JPanel(new FlowLayout());
567 pnl_fileChooser.add(btn_pdbFromFile);
568 pnl_fileChooser.add(lbl_fromFileStatus);
569 JPanel pnl_fileChooserBL = new JPanel(new BorderLayout());
570 pnl_fileChooserBL.add(fileChooserAssSeqPanel, BorderLayout.NORTH);
571 pnl_fileChooserBL.add(pnl_fileChooser, BorderLayout.CENTER);
573 JPanel pnl_idInput = new JPanel(new FlowLayout());
574 pnl_idInput.add(txt_search);
575 pnl_idInput.add(lbl_pdbManualFetchStatus);
577 JPanel pnl_idInputBL = new JPanel(new BorderLayout());
578 pnl_idInputBL.add(idInputAssSeqPanel, BorderLayout.NORTH);
579 pnl_idInputBL.add(pnl_idInput, BorderLayout.CENTER);
581 final String foundStructureSummary = MessageManager
582 .getString("label.found_structures_summary");
583 final String configureCols = MessageManager
584 .getString("label.configure_displayed_columns");
585 ChangeListener changeListener = new ChangeListener()
588 public void stateChanged(ChangeEvent changeEvent)
590 JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent
592 int index = sourceTabbedPane.getSelectedIndex();
593 btn_add.setVisible(targetView.isVisible());
594 btn_newView.setVisible(true);
595 btn_cancel.setVisible(true);
596 if (sourceTabbedPane.getTitleAt(index).equals(configureCols))
598 btn_add.setEnabled(false);
599 btn_cancel.setEnabled(false);
600 btn_add.setVisible(false);
601 btn_newView.setEnabled(false);
602 btn_cancel.setVisible(false);
603 previousWantedFields = getFTSDocFieldPrefs()
604 .getStructureSummaryFields()
605 .toArray(new FTSDataColumnI[0]);
607 if (sourceTabbedPane.getTitleAt(index)
608 .equals(foundStructureSummary))
610 btn_cancel.setEnabled(true);
611 if (wantedFieldsUpdated())
617 validateSelections();
622 pnl_filter.addChangeListener(changeListener);
623 pnl_filter.setPreferredSize(new Dimension(width, height));
624 pnl_filter.add(foundStructureSummary, scrl_foundStructures);
625 pnl_filter.add(configureCols, getFTSDocFieldPrefs());
627 JPanel pnl_locPDB = new JPanel(new BorderLayout());
628 pnl_locPDB.add(scrl_localPDB);
630 pnl_switchableViews.add(pnl_fileChooserBL, VIEWS_FROM_FILE);
631 pnl_switchableViews.add(pnl_idInputBL, VIEWS_ENTER_ID);
632 pnl_switchableViews.add(pnl_filter, VIEWS_FILTER);
633 pnl_switchableViews.add(pnl_locPDB, VIEWS_LOCAL_PDB);
635 this.setLayout(new BorderLayout());
636 this.add(pnl_main, java.awt.BorderLayout.NORTH);
637 this.add(pnl_switchableViews, java.awt.BorderLayout.CENTER);
638 // this.add(pnl_actions, java.awt.BorderLayout.SOUTH);
639 statusPanel.setLayout(new GridLayout());
641 JPanel pnl_actionsAndStatus = new JPanel(new BorderLayout());
642 pnl_actionsAndStatus.add(actionsPanel, BorderLayout.CENTER);
643 pnl_actionsAndStatus.add(statusPanel, BorderLayout.SOUTH);
644 statusPanel.add(statusBar, null);
645 this.add(pnl_actionsAndStatus, java.awt.BorderLayout.SOUTH);
647 mainFrame.addInternalFrameListener(
648 new javax.swing.event.InternalFrameAdapter()
651 public void internalFrameClosing(InternalFrameEvent e)
653 closeAction(pnl_filter.getHeight());
656 mainFrame.setVisible(true);
657 mainFrame.setContentPane(this);
658 mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
659 Integer x = tempUserPrefs.get("structureChooser.x");
660 Integer y = tempUserPrefs.get("structureChooser.y");
661 if (x != null && y != null)
663 mainFrame.setLocation(x, y);
665 Desktop.addInternalFrame(mainFrame, frameTitle, width, height);
669 protected void closeAction(int preferredHeight)
671 // System.out.println(">>>>>>>>>> closing internal frame!!!");
672 // System.out.println("width : " + mainFrame.getWidth());
673 // System.out.println("heigh : " + mainFrame.getHeight());
674 // System.out.println("x : " + mainFrame.getX());
675 // System.out.println("y : " + mainFrame.getY());
676 tempUserPrefs.put("structureChooser.width", pnl_filter.getWidth());
677 tempUserPrefs.put("structureChooser.height", preferredHeight);
678 tempUserPrefs.put("structureChooser.x", mainFrame.getX());
679 tempUserPrefs.put("structureChooser.y", mainFrame.getY());
683 public boolean wantedFieldsUpdated()
685 if (previousWantedFields == null)
690 FTSDataColumnI[] currentWantedFields = getFTSDocFieldPrefs()
691 .getStructureSummaryFields().toArray(new FTSDataColumnI[0]);
692 return Arrays.equals(currentWantedFields, previousWantedFields) ? false
699 * Event listener for the 'filter' combo-box and 'invert' check-box
701 public void itemStateChanged(ItemEvent e)
707 * This inner class provides the data model for the structure filter combo-box
712 public class FilterOption
716 private String value;
720 private boolean addSeparatorAfter;
723 * Model for structure filter option
726 * - the name of the Option
728 * - the value of the option
730 * - the category of the filter option
731 * @param addSeparatorAfter
732 * - if true, a horizontal separator is rendered immediately after
733 * this filter option, otherwise
735 public FilterOption(String name, String value, String view,
736 boolean addSeparatorAfter)
741 this.addSeparatorAfter = addSeparatorAfter;
744 public String getName()
749 public void setName(String name)
754 public String getValue()
759 public void setValue(String value)
764 public String getView()
769 public void setView(String view)
775 public String toString()
780 public boolean isAddSeparatorAfter()
782 return addSeparatorAfter;
785 public void setAddSeparatorAfter(boolean addSeparatorAfter)
787 this.addSeparatorAfter = addSeparatorAfter;
792 * This inner class provides the provides the data model for associate
793 * sequence combo-box - cmb_assSeq
798 public class AssociateSeqOptions
800 private SequenceI sequence;
804 public AssociateSeqOptions(SequenceI seq)
807 this.name = (seq.getName().length() >= 23)
808 ? seq.getName().substring(0, 23)
812 public AssociateSeqOptions(String name, SequenceI seq)
819 public String toString()
824 public String getName()
829 public void setName(String name)
834 public SequenceI getSequence()
839 public void setSequence(SequenceI sequence)
841 this.sequence = sequence;
847 * This inner class holds the Layout and configuration of the panel which
848 * handles association of manually fetched structures to a unique sequence
849 * when more than one sequence selection is made
854 public class AssociateSeqPanel extends JPanel implements ItemListener
856 private JComboBox<AssociateSeqOptions> cmb_assSeq = new JComboBox<>();
858 private JLabel lbl_associateSeq = new JLabel();
860 public AssociateSeqPanel()
862 this.setLayout(new FlowLayout());
863 this.add(cmb_assSeq);
864 this.add(lbl_associateSeq);
865 cmb_assSeq.setToolTipText(
866 MessageManager.getString("info.associate_wit_sequence"));
867 cmb_assSeq.addItemListener(this);
870 public void loadCmbAssSeq()
872 populateCmbAssociateSeqOptions(cmb_assSeq, lbl_associateSeq);
875 public JComboBox<AssociateSeqOptions> getCmb_assSeq()
880 public void setCmb_assSeq(JComboBox<AssociateSeqOptions> cmb_assSeq)
882 this.cmb_assSeq = cmb_assSeq;
886 public void itemStateChanged(ItemEvent e)
888 if (e.getStateChange() == ItemEvent.SELECTED)
890 cmbAssSeqStateChanged();
895 public JTable getResultTable()
900 public JComboBox<FilterOption> getCmbFilterOption()
902 return cmb_filterOption;
906 * Custom ListCellRenderer for adding a separator between different categories
907 * of structure chooser filter option drop-down.
912 public abstract class CustomComboSeparatorsRenderer
913 implements ListCellRenderer<Object>
915 private ListCellRenderer<Object> regent;
917 private JPanel separatorPanel = new JPanel(new BorderLayout());
919 private JSeparator jSeparator = new JSeparator();
921 public CustomComboSeparatorsRenderer(
922 ListCellRenderer<Object> listCellRenderer)
924 this.regent = listCellRenderer;
928 public Component getListCellRendererComponent(JList list, Object value,
929 int index, boolean isSelected, boolean cellHasFocus)
932 Component comp = regent.getListCellRendererComponent(list, value,
933 index, isSelected, cellHasFocus);
935 && addSeparatorAfter(list, (FilterOption) value, index))
937 separatorPanel.removeAll();
938 separatorPanel.add(comp, BorderLayout.CENTER);
939 separatorPanel.add(jSeparator, BorderLayout.SOUTH);
940 return separatorPanel;
948 protected abstract boolean addSeparatorAfter(JList list,
949 FilterOption value, int index);
952 protected abstract void stateChanged(ItemEvent e);
954 protected abstract void add_ActionPerformed();
956 protected abstract void newView_ActionPerformed();
958 protected abstract void pdbFromFile_actionPerformed();
960 protected abstract void txt_search_ActionPerformed();
962 protected abstract void populateCmbAssociateSeqOptions(
963 JComboBox<AssociateSeqOptions> cmb_assSeq,
964 JLabel lbl_associateSeq);
966 protected abstract void cmbAssSeqStateChanged();
968 protected abstract void tabRefresh();
970 protected abstract void validateSelections();