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.beans.PropertyChangeEvent;
51 import java.beans.PropertyChangeListener;
52 import java.util.Arrays;
53 import java.util.HashMap;
56 import javax.swing.ImageIcon;
57 import javax.swing.JButton;
58 import javax.swing.JCheckBox;
59 import javax.swing.JComboBox;
60 import javax.swing.JFrame;
61 import javax.swing.JInternalFrame;
62 import javax.swing.JLabel;
63 import javax.swing.JList;
64 import javax.swing.JPanel;
65 import javax.swing.JScrollPane;
66 import javax.swing.JSeparator;
67 import javax.swing.JTabbedPane;
68 import javax.swing.JTable;
69 import javax.swing.JTextField;
70 import javax.swing.ListCellRenderer;
71 import javax.swing.SwingUtilities;
72 import javax.swing.Timer;
73 import javax.swing.event.ChangeEvent;
74 import javax.swing.event.ChangeListener;
75 import javax.swing.event.DocumentEvent;
76 import javax.swing.event.DocumentListener;
77 import javax.swing.event.InternalFrameEvent;
78 import javax.swing.table.TableColumn;
80 import net.miginfocom.swing.MigLayout;
82 @SuppressWarnings("serial")
84 * GUI layout for structure chooser
89 public abstract class GStructureChooser extends JPanel
90 implements ItemListener
92 private static final Font VERDANA_12 = new Font("Verdana", 0, 12);
94 protected static final String VIEWS_FILTER = "VIEWS_FILTER";
96 protected static final String VIEWS_FROM_FILE = "VIEWS_FROM_FILE";
98 protected static final String VIEWS_ENTER_ID = "VIEWS_ENTER_ID";
101 * 'cached' structure view
103 protected static final String VIEWS_LOCAL_PDB = "VIEWS_LOCAL_PDB";
105 protected JPanel statusPanel = new JPanel();
107 public JLabel statusBar = new JLabel();
109 protected String frameTitle = MessageManager
110 .getString("label.structure_chooser");
112 protected JInternalFrame mainFrame = new JInternalFrame(frameTitle);
114 protected JComboBox<FilterOption> cmb_filterOption = new JComboBox<>();
116 protected AlignmentPanel ap;
118 protected StringBuilder errorWarning = new StringBuilder();
120 protected JButton btn_add;
122 protected JButton btn_newView;
124 protected JButton btn_pdbFromFile = new JButton();
126 protected JCheckBox chk_superpose = new JCheckBox(
127 MessageManager.getString("label.superpose_structures"));
129 protected JTextField txt_search = new JTextField(14);
131 protected JPanel pnl_switchableViews = new JPanel(new CardLayout());
133 protected CardLayout layout_switchableViews = (CardLayout) (pnl_switchableViews
136 protected JCheckBox chk_invertFilter = new JCheckBox(
137 MessageManager.getString("label.invert"));
139 protected ImageIcon loadingImage = new ImageIcon(
140 getClass().getResource("/images/loading.gif"));
142 protected ImageIcon goodImage = new ImageIcon(
143 getClass().getResource("/images/good.png"));
145 protected ImageIcon errorImage = new ImageIcon(
146 getClass().getResource("/images/error.png"));
148 protected ImageIcon warningImage = new ImageIcon(
149 getClass().getResource("/images/warning.gif"));
151 protected JLabel lbl_loading = new JLabel(loadingImage);
153 protected JLabel lbl_pdbManualFetchStatus = new JLabel(errorImage);
155 protected JLabel lbl_fromFileStatus = new JLabel(errorImage);
157 protected AssociateSeqPanel idInputAssSeqPanel = new AssociateSeqPanel();
159 protected AssociateSeqPanel fileChooserAssSeqPanel = new AssociateSeqPanel();
161 protected JComboBox<StructureViewer> targetView = new JComboBox<>();
163 protected JTable tbl_local_pdb = new JTable();
165 protected JTabbedPane pnl_filter = jalview.jbgui.GDesktop.createTabbedPane();
167 protected FTSDataColumnPreferences pdbDocFieldPrefs = new FTSDataColumnPreferences(
168 PreferenceSource.STRUCTURE_CHOOSER,
169 PDBFTSRestClient.getInstance());
171 protected FTSDataColumnI[] previousWantedFields;
173 protected static Map<String, Integer> tempUserPrefs = new HashMap<>();
175 private JTable tbl_summary = new JTable()
177 private boolean inLayout;
180 public boolean getScrollableTracksViewportWidth()
182 return hasExcessWidth();
187 public void doLayout()
189 if (hasExcessWidth())
191 autoResizeMode = AUTO_RESIZE_SUBSEQUENT_COLUMNS;
196 autoResizeMode = AUTO_RESIZE_OFF;
199 protected boolean hasExcessWidth()
201 return getPreferredSize().width < getParent().getWidth();
205 public void columnMarginChanged(ChangeEvent e)
211 TableColumn resizingColumn = getTableHeader().getResizingColumn();
212 // Need to do this here, before the parent's
213 // layout manager calls getPreferredSize().
214 if (resizingColumn != null && autoResizeMode == AUTO_RESIZE_OFF
217 resizingColumn.setPreferredWidth(resizingColumn.getWidth());
218 String colHeader = resizingColumn.getHeaderValue().toString();
219 tempUserPrefs.put(colHeader, resizingColumn.getWidth());
225 public String getToolTipText(MouseEvent evt)
227 String toolTipText = null;
228 java.awt.Point pnt = evt.getPoint();
229 int rowIndex = rowAtPoint(pnt);
230 int colIndex = columnAtPoint(pnt);
234 if (getValueAt(rowIndex, colIndex) == null)
238 toolTipText = getValueAt(rowIndex, colIndex).toString();
239 } catch (Exception e)
241 // e.printStackTrace();
243 toolTipText = (toolTipText == null ? null
244 : (toolTipText.length() > 500
245 ? JvSwingUtils.wrapTooltip(true,
246 "\"" + toolTipText.subSequence(0, 500)
248 : JvSwingUtils.wrapTooltip(true, toolTipText)));
253 public GStructureChooser()
258 mainFrame.setVisible(false);
259 mainFrame.invalidate();
261 } catch (Exception e)
263 System.out.println(e); // for JavaScript TypeError
268 // BH SwingJS optimization
269 // (a) 100-ms interruptable timer for text entry -- BH 1/10/2019
270 // (b) two-character minimum, at least for JavaScript.
274 protected void txt_search_ActionPerformedDelayed() {
278 timer = new Timer(300, new ActionListener() {
281 public void actionPerformed(ActionEvent e) {
282 txt_search_ActionPerformed();
285 timer.setRepeats(false);
291 * Initializes the GUI default properties
295 private void jbInit() throws Exception
297 Integer width = tempUserPrefs.get("structureChooser.width") == null
299 : tempUserPrefs.get("structureChooser.width");
300 Integer height = tempUserPrefs.get("structureChooser.height") == null
302 : tempUserPrefs.get("structureChooser.height");
303 tbl_summary.setAutoCreateRowSorter(true);
304 tbl_summary.getTableHeader().setReorderingAllowed(false);
305 tbl_summary.addMouseListener(new MouseAdapter()
308 public void mouseClicked(MouseEvent e)
310 validateSelections();
314 public void mouseReleased(MouseEvent e)
316 validateSelections();
319 tbl_summary.addKeyListener(new KeyAdapter()
322 public void keyPressed(KeyEvent evt)
324 validateSelections();
325 switch (evt.getKeyCode())
327 case KeyEvent.VK_ESCAPE: // escape key
330 case KeyEvent.VK_ENTER: // enter key
331 if (btn_add.isEnabled())
333 add_ActionPerformed();
336 case KeyEvent.VK_TAB: // tab key
337 if (evt.isShiftDown())
339 pnl_filter.requestFocus();
343 btn_add.requestFocus();
353 JButton btn_cancel = new JButton(
354 MessageManager.getString("action.cancel"));
355 btn_cancel.setFont(VERDANA_12);
356 btn_cancel.addActionListener(new java.awt.event.ActionListener()
359 public void actionPerformed(ActionEvent e)
361 closeAction(pnl_filter.getHeight());
364 btn_cancel.addKeyListener(new KeyAdapter()
367 public void keyPressed(KeyEvent evt)
369 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
371 closeAction(pnl_filter.getHeight());
376 tbl_local_pdb.setAutoCreateRowSorter(true);
377 tbl_local_pdb.getTableHeader().setReorderingAllowed(false);
378 tbl_local_pdb.addMouseListener(new MouseAdapter()
381 public void mouseClicked(MouseEvent e)
383 validateSelections();
387 public void mouseReleased(MouseEvent e)
389 validateSelections();
392 tbl_local_pdb.addKeyListener(new KeyAdapter()
395 public void keyPressed(KeyEvent evt)
397 validateSelections();
398 switch (evt.getKeyCode())
400 case KeyEvent.VK_ESCAPE: // escape key
403 case KeyEvent.VK_ENTER: // enter key
404 if (btn_add.isEnabled())
406 add_ActionPerformed();
409 case KeyEvent.VK_TAB: // tab key
410 if (evt.isShiftDown())
412 cmb_filterOption.requestFocus();
416 if (btn_add.isEnabled())
418 btn_add.requestFocus();
422 btn_cancel.requestFocus();
433 btn_newView = new JButton(MessageManager.getString("action.new_view"));
434 btn_newView.setFont(VERDANA_12);
435 btn_newView.addActionListener(new java.awt.event.ActionListener()
438 public void actionPerformed(ActionEvent e)
440 newView_ActionPerformed();
443 btn_newView.addKeyListener(new KeyAdapter()
446 public void keyPressed(KeyEvent evt)
448 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
450 newView_ActionPerformed();
455 btn_add = new JButton(MessageManager.getString("action.add"));
456 btn_add.setFont(VERDANA_12);
457 btn_add.addActionListener(new java.awt.event.ActionListener()
460 public void actionPerformed(ActionEvent e)
462 add_ActionPerformed();
465 btn_add.addKeyListener(new KeyAdapter()
468 public void keyPressed(KeyEvent evt)
470 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
472 add_ActionPerformed();
477 btn_pdbFromFile.setFont(VERDANA_12);
478 String btn_title = MessageManager.getString("label.select_pdb_file");
479 btn_pdbFromFile.setText(btn_title + " ");
480 btn_pdbFromFile.addActionListener(new java.awt.event.ActionListener()
483 public void actionPerformed(ActionEvent e)
485 pdbFromFile_actionPerformed();
488 btn_pdbFromFile.addKeyListener(new KeyAdapter()
491 public void keyPressed(KeyEvent evt)
493 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
495 pdbFromFile_actionPerformed();
500 JScrollPane scrl_foundStructures = new JScrollPane(tbl_summary);
501 scrl_foundStructures.setPreferredSize(new Dimension(width, height));
503 JScrollPane scrl_localPDB = new JScrollPane(tbl_local_pdb);
504 scrl_localPDB.setPreferredSize(new Dimension(width, height));
505 scrl_localPDB.setHorizontalScrollBarPolicy(
506 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
508 chk_invertFilter.setFont(VERDANA_12);
509 txt_search.setToolTipText(JvSwingUtils.wrapTooltip(true,
510 MessageManager.getString("label.enter_pdb_id_tip")));
511 txt_search.getDocument().addDocumentListener(new DocumentListener()
514 public void insertUpdate(DocumentEvent e)
516 txt_search_ActionPerformedDelayed();
520 public void removeUpdate(DocumentEvent e)
522 txt_search_ActionPerformedDelayed();
526 public void changedUpdate(DocumentEvent e)
528 txt_search_ActionPerformedDelayed();
532 cmb_filterOption.setFont(VERDANA_12);
533 cmb_filterOption.setToolTipText(
534 MessageManager.getString("info.select_filter_option"));
535 cmb_filterOption.addItemListener(this);
536 // add CustomComboSeparatorsRenderer to filter option combo-box
537 cmb_filterOption.setRenderer(new CustomComboSeparatorsRenderer(
538 (ListCellRenderer<Object>) cmb_filterOption.getRenderer())
541 protected boolean addSeparatorAfter(JList list, FilterOption value,
544 return value.isAddSeparatorAfter();
548 chk_invertFilter.addItemListener(this);
550 targetView.setVisible(false);
552 JPanel actionsPanel = new JPanel(new MigLayout());
553 actionsPanel.add(targetView, "left");
554 actionsPanel.add(btn_add, "wrap");
555 actionsPanel.add(chk_superpose, "left");
556 actionsPanel.add(btn_newView);
557 actionsPanel.add(btn_cancel, "right");
559 JPanel pnl_main = new JPanel();
560 pnl_main.add(cmb_filterOption);
561 pnl_main.add(lbl_loading);
562 pnl_main.add(chk_invertFilter);
563 lbl_loading.setVisible(false);
565 JPanel pnl_fileChooser = new JPanel(new FlowLayout());
566 pnl_fileChooser.add(btn_pdbFromFile);
567 pnl_fileChooser.add(lbl_fromFileStatus);
568 JPanel pnl_fileChooserBL = new JPanel(new BorderLayout());
569 pnl_fileChooserBL.add(fileChooserAssSeqPanel, BorderLayout.NORTH);
570 pnl_fileChooserBL.add(pnl_fileChooser, BorderLayout.CENTER);
572 JPanel pnl_idInput = new JPanel(new FlowLayout());
573 pnl_idInput.add(txt_search);
574 pnl_idInput.add(lbl_pdbManualFetchStatus);
576 JPanel pnl_idInputBL = new JPanel(new BorderLayout());
577 pnl_idInputBL.add(idInputAssSeqPanel, BorderLayout.NORTH);
578 pnl_idInputBL.add(pnl_idInput, BorderLayout.CENTER);
580 final String foundStructureSummary = MessageManager
581 .getString("label.found_structures_summary");
582 final String configureCols = MessageManager
583 .getString("label.configure_displayed_columns");
584 ChangeListener changeListener = new ChangeListener()
587 public void stateChanged(ChangeEvent changeEvent)
589 JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent
591 int index = sourceTabbedPane.getSelectedIndex();
592 btn_add.setVisible(targetView.isVisible());
593 btn_newView.setVisible(true);
594 btn_cancel.setVisible(true);
595 if (sourceTabbedPane.getTitleAt(index).equals(configureCols))
597 btn_add.setEnabled(false);
598 btn_cancel.setEnabled(false);
599 btn_add.setVisible(false);
600 btn_newView.setEnabled(false);
601 btn_cancel.setVisible(false);
602 previousWantedFields = pdbDocFieldPrefs
603 .getStructureSummaryFields()
604 .toArray(new FTSDataColumnI[0]);
606 if (sourceTabbedPane.getTitleAt(index)
607 .equals(foundStructureSummary))
609 btn_cancel.setEnabled(true);
610 if (wantedFieldsUpdated())
616 validateSelections();
621 pnl_filter.addChangeListener(changeListener);
622 pnl_filter.setPreferredSize(new Dimension(width, height));
623 pnl_filter.add(foundStructureSummary, scrl_foundStructures);
624 pnl_filter.add(configureCols, pdbDocFieldPrefs);
626 JPanel pnl_locPDB = new JPanel(new BorderLayout());
627 pnl_locPDB.add(scrl_localPDB);
629 pnl_switchableViews.add(pnl_fileChooserBL, VIEWS_FROM_FILE);
630 pnl_switchableViews.add(pnl_idInputBL, VIEWS_ENTER_ID);
631 pnl_switchableViews.add(pnl_filter, VIEWS_FILTER);
632 pnl_switchableViews.add(pnl_locPDB, VIEWS_LOCAL_PDB);
634 this.setLayout(new BorderLayout());
635 this.add(pnl_main, java.awt.BorderLayout.NORTH);
636 this.add(pnl_switchableViews, java.awt.BorderLayout.CENTER);
637 // this.add(pnl_actions, java.awt.BorderLayout.SOUTH);
638 statusPanel.setLayout(new GridLayout());
640 JPanel pnl_actionsAndStatus = new JPanel(new BorderLayout());
641 pnl_actionsAndStatus.add(actionsPanel, BorderLayout.CENTER);
642 pnl_actionsAndStatus.add(statusPanel, BorderLayout.SOUTH);
643 statusPanel.add(statusBar, null);
644 this.add(pnl_actionsAndStatus, java.awt.BorderLayout.SOUTH);
646 mainFrame.addInternalFrameListener(
647 new javax.swing.event.InternalFrameAdapter()
650 public void internalFrameClosing(InternalFrameEvent e)
652 closeAction(pnl_filter.getHeight());
655 mainFrame.setVisible(true);
656 mainFrame.setContentPane(this);
657 mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
658 Integer x = tempUserPrefs.get("structureChooser.x");
659 Integer y = tempUserPrefs.get("structureChooser.y");
660 if (x != null && y != null)
662 mainFrame.setLocation(x, y);
664 Desktop.addInternalFrame(mainFrame, frameTitle, width, height);
668 protected void closeAction(int preferredHeight)
670 // System.out.println(">>>>>>>>>> closing internal frame!!!");
671 // System.out.println("width : " + mainFrame.getWidth());
672 // System.out.println("heigh : " + mainFrame.getHeight());
673 // System.out.println("x : " + mainFrame.getX());
674 // System.out.println("y : " + mainFrame.getY());
675 tempUserPrefs.put("structureChooser.width", pnl_filter.getWidth());
676 tempUserPrefs.put("structureChooser.height", preferredHeight);
677 tempUserPrefs.put("structureChooser.x", mainFrame.getX());
678 tempUserPrefs.put("structureChooser.y", mainFrame.getY());
682 public boolean wantedFieldsUpdated()
684 if (previousWantedFields == null)
689 FTSDataColumnI[] currentWantedFields = pdbDocFieldPrefs
690 .getStructureSummaryFields().toArray(new FTSDataColumnI[0]);
691 return Arrays.equals(currentWantedFields, previousWantedFields) ? false
698 * Event listener for the 'filter' combo-box and 'invert' check-box
700 public void itemStateChanged(ItemEvent e)
706 * This inner class provides the data model for the structure filter combo-box
711 public class FilterOption
715 private String value;
719 private boolean addSeparatorAfter;
722 * Model for structure filter option
725 * - the name of the Option
727 * - the value of the option
729 * - the category of the filter option
730 * @param addSeparatorAfter
731 * - if true, a horizontal separator is rendered immediately after
732 * this filter option, otherwise
734 public FilterOption(String name, String value, String view,
735 boolean addSeparatorAfter)
740 this.addSeparatorAfter = addSeparatorAfter;
743 public String getName()
748 public void setName(String name)
753 public String getValue()
758 public void setValue(String value)
763 public String getView()
768 public void setView(String view)
774 public String toString()
779 public boolean isAddSeparatorAfter()
781 return addSeparatorAfter;
784 public void setAddSeparatorAfter(boolean addSeparatorAfter)
786 this.addSeparatorAfter = addSeparatorAfter;
791 * This inner class provides the provides the data model for associate
792 * sequence combo-box - cmb_assSeq
797 public class AssociateSeqOptions
799 private SequenceI sequence;
803 public AssociateSeqOptions(SequenceI seq)
806 this.name = (seq.getName().length() >= 23)
807 ? seq.getName().substring(0, 23)
811 public AssociateSeqOptions(String name, SequenceI seq)
818 public String toString()
823 public String getName()
828 public void setName(String name)
833 public SequenceI getSequence()
838 public void setSequence(SequenceI sequence)
840 this.sequence = sequence;
846 * This inner class holds the Layout and configuration of the panel which
847 * handles association of manually fetched structures to a unique sequence
848 * when more than one sequence selection is made
853 public class AssociateSeqPanel extends JPanel implements ItemListener
855 private JComboBox<AssociateSeqOptions> cmb_assSeq = new JComboBox<>();
857 private JLabel lbl_associateSeq = new JLabel();
859 public AssociateSeqPanel()
861 this.setLayout(new FlowLayout());
862 this.add(cmb_assSeq);
863 this.add(lbl_associateSeq);
864 cmb_assSeq.setToolTipText(
865 MessageManager.getString("info.associate_wit_sequence"));
866 cmb_assSeq.addItemListener(this);
869 public void loadCmbAssSeq()
871 populateCmbAssociateSeqOptions(cmb_assSeq, lbl_associateSeq);
874 public JComboBox<AssociateSeqOptions> getCmb_assSeq()
879 public void setCmb_assSeq(JComboBox<AssociateSeqOptions> cmb_assSeq)
881 this.cmb_assSeq = cmb_assSeq;
885 public void itemStateChanged(ItemEvent e)
887 if (e.getStateChange() == ItemEvent.SELECTED)
889 cmbAssSeqStateChanged();
894 public JTable getResultTable()
899 public JComboBox<FilterOption> getCmbFilterOption()
901 return cmb_filterOption;
905 * Custom ListCellRenderer for adding a separator between different categories
906 * of structure chooser filter option drop-down.
911 public abstract class CustomComboSeparatorsRenderer
912 implements ListCellRenderer<Object>
914 private ListCellRenderer<Object> regent;
916 private JPanel separatorPanel = new JPanel(new BorderLayout());
918 private JSeparator jSeparator = new JSeparator();
920 public CustomComboSeparatorsRenderer(
921 ListCellRenderer<Object> listCellRenderer)
923 this.regent = listCellRenderer;
927 public Component getListCellRendererComponent(JList list, Object value,
928 int index, boolean isSelected, boolean cellHasFocus)
931 Component comp = regent.getListCellRendererComponent(list, value,
932 index, isSelected, cellHasFocus);
934 && addSeparatorAfter(list, (FilterOption) value, index))
936 separatorPanel.removeAll();
937 separatorPanel.add(comp, BorderLayout.CENTER);
938 separatorPanel.add(jSeparator, BorderLayout.SOUTH);
939 return separatorPanel;
947 protected abstract boolean addSeparatorAfter(JList list,
948 FilterOption value, int index);
951 protected abstract void stateChanged(ItemEvent e);
953 protected abstract void add_ActionPerformed();
955 protected abstract void newView_ActionPerformed();
957 protected abstract void pdbFromFile_actionPerformed();
959 protected abstract void txt_search_ActionPerformed();
961 protected abstract void populateCmbAssociateSeqOptions(
962 JComboBox<AssociateSeqOptions> cmb_assSeq,
963 JLabel lbl_associateSeq);
965 protected abstract void cmbAssSeqStateChanged();
967 protected abstract void tabRefresh();
969 protected abstract void validateSelections();