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 FTSDataColumnPreferences pdbDocFieldPrefs = new FTSDataColumnPreferences(
165 PreferenceSource.STRUCTURE_CHOOSER,
166 PDBFTSRestClient.getInstance());
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()
255 mainFrame.setVisible(false);
256 mainFrame.invalidate();
258 } catch (Exception e)
260 System.out.println(e); // for JavaScript TypeError
265 // BH SwingJS optimization
266 // (a) 100-ms interruptable timer for text entry -- BH 1/10/2019
267 // (b) two-character minimum, at least for JavaScript.
271 protected void txt_search_ActionPerformedDelayed() {
275 timer = new Timer(300, new ActionListener() {
278 public void actionPerformed(ActionEvent e) {
279 txt_search_ActionPerformed();
282 timer.setRepeats(false);
288 * Initializes the GUI default properties
292 private void jbInit() throws Exception
294 Integer width = tempUserPrefs.get("structureChooser.width") == null
296 : tempUserPrefs.get("structureChooser.width");
297 Integer height = tempUserPrefs.get("structureChooser.height") == null
299 : tempUserPrefs.get("structureChooser.height");
300 tbl_summary.setAutoCreateRowSorter(true);
301 tbl_summary.getTableHeader().setReorderingAllowed(false);
302 tbl_summary.addMouseListener(new MouseAdapter()
305 public void mouseClicked(MouseEvent e)
307 validateSelections();
311 public void mouseReleased(MouseEvent e)
313 validateSelections();
316 tbl_summary.addKeyListener(new KeyAdapter()
319 public void keyPressed(KeyEvent evt)
321 validateSelections();
322 switch (evt.getKeyCode())
324 case KeyEvent.VK_ESCAPE: // escape key
327 case KeyEvent.VK_ENTER: // enter key
328 if (btn_add.isEnabled())
330 add_ActionPerformed();
333 case KeyEvent.VK_TAB: // tab key
334 if (evt.isShiftDown())
336 pnl_filter.requestFocus();
340 btn_add.requestFocus();
350 JButton btn_cancel = new JButton(
351 MessageManager.getString("action.cancel"));
352 btn_cancel.setFont(VERDANA_12);
353 btn_cancel.addActionListener(new java.awt.event.ActionListener()
356 public void actionPerformed(ActionEvent e)
358 closeAction(pnl_filter.getHeight());
361 btn_cancel.addKeyListener(new KeyAdapter()
364 public void keyPressed(KeyEvent evt)
366 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
368 closeAction(pnl_filter.getHeight());
373 tbl_local_pdb.setAutoCreateRowSorter(true);
374 tbl_local_pdb.getTableHeader().setReorderingAllowed(false);
375 tbl_local_pdb.addMouseListener(new MouseAdapter()
378 public void mouseClicked(MouseEvent e)
380 validateSelections();
384 public void mouseReleased(MouseEvent e)
386 validateSelections();
389 tbl_local_pdb.addKeyListener(new KeyAdapter()
392 public void keyPressed(KeyEvent evt)
394 validateSelections();
395 switch (evt.getKeyCode())
397 case KeyEvent.VK_ESCAPE: // escape key
400 case KeyEvent.VK_ENTER: // enter key
401 if (btn_add.isEnabled())
403 add_ActionPerformed();
406 case KeyEvent.VK_TAB: // tab key
407 if (evt.isShiftDown())
409 cmb_filterOption.requestFocus();
413 if (btn_add.isEnabled())
415 btn_add.requestFocus();
419 btn_cancel.requestFocus();
430 btn_newView = new JButton(MessageManager.getString("action.new_view"));
431 btn_newView.setFont(VERDANA_12);
432 btn_newView.addActionListener(new java.awt.event.ActionListener()
435 public void actionPerformed(ActionEvent e)
437 newView_ActionPerformed();
440 btn_newView.addKeyListener(new KeyAdapter()
443 public void keyPressed(KeyEvent evt)
445 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
447 newView_ActionPerformed();
452 btn_add = new JButton(MessageManager.getString("action.add"));
453 btn_add.setFont(VERDANA_12);
454 btn_add.addActionListener(new java.awt.event.ActionListener()
457 public void actionPerformed(ActionEvent e)
459 add_ActionPerformed();
462 btn_add.addKeyListener(new KeyAdapter()
465 public void keyPressed(KeyEvent evt)
467 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
469 add_ActionPerformed();
474 btn_pdbFromFile.setFont(VERDANA_12);
475 String btn_title = MessageManager.getString("label.select_pdb_file");
476 btn_pdbFromFile.setText(btn_title + " ");
477 btn_pdbFromFile.addActionListener(new java.awt.event.ActionListener()
480 public void actionPerformed(ActionEvent e)
482 pdbFromFile_actionPerformed();
485 btn_pdbFromFile.addKeyListener(new KeyAdapter()
488 public void keyPressed(KeyEvent evt)
490 if (evt.getKeyCode() == KeyEvent.VK_ENTER)
492 pdbFromFile_actionPerformed();
497 JScrollPane scrl_foundStructures = new JScrollPane(tbl_summary);
498 scrl_foundStructures.setPreferredSize(new Dimension(width, height));
500 JScrollPane scrl_localPDB = new JScrollPane(tbl_local_pdb);
501 scrl_localPDB.setPreferredSize(new Dimension(width, height));
502 scrl_localPDB.setHorizontalScrollBarPolicy(
503 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
505 chk_invertFilter.setFont(VERDANA_12);
506 txt_search.setToolTipText(JvSwingUtils.wrapTooltip(true,
507 MessageManager.getString("label.enter_pdb_id_tip")));
508 txt_search.getDocument().addDocumentListener(new DocumentListener()
511 public void insertUpdate(DocumentEvent e)
513 txt_search_ActionPerformedDelayed();
517 public void removeUpdate(DocumentEvent e)
519 txt_search_ActionPerformedDelayed();
523 public void changedUpdate(DocumentEvent e)
525 txt_search_ActionPerformedDelayed();
529 cmb_filterOption.setFont(VERDANA_12);
530 cmb_filterOption.setToolTipText(
531 MessageManager.getString("info.select_filter_option"));
532 cmb_filterOption.addItemListener(this);
533 // add CustomComboSeparatorsRenderer to filter option combo-box
534 cmb_filterOption.setRenderer(new CustomComboSeparatorsRenderer(
535 (ListCellRenderer<Object>) cmb_filterOption.getRenderer())
538 protected boolean addSeparatorAfter(JList list, FilterOption value,
541 return value.isAddSeparatorAfter();
545 chk_invertFilter.addItemListener(this);
547 targetView.setVisible(false);
549 JPanel actionsPanel = new JPanel(new MigLayout());
550 actionsPanel.add(targetView, "left");
551 actionsPanel.add(btn_add, "wrap");
552 actionsPanel.add(chk_superpose, "left");
553 actionsPanel.add(btn_newView);
554 actionsPanel.add(btn_cancel, "right");
556 JPanel pnl_main = new JPanel();
557 pnl_main.add(cmb_filterOption);
558 pnl_main.add(lbl_loading);
559 pnl_main.add(chk_invertFilter);
560 lbl_loading.setVisible(false);
562 JPanel pnl_fileChooser = new JPanel(new FlowLayout());
563 pnl_fileChooser.add(btn_pdbFromFile);
564 pnl_fileChooser.add(lbl_fromFileStatus);
565 JPanel pnl_fileChooserBL = new JPanel(new BorderLayout());
566 pnl_fileChooserBL.add(fileChooserAssSeqPanel, BorderLayout.NORTH);
567 pnl_fileChooserBL.add(pnl_fileChooser, BorderLayout.CENTER);
569 JPanel pnl_idInput = new JPanel(new FlowLayout());
570 pnl_idInput.add(txt_search);
571 pnl_idInput.add(lbl_pdbManualFetchStatus);
573 JPanel pnl_idInputBL = new JPanel(new BorderLayout());
574 pnl_idInputBL.add(idInputAssSeqPanel, BorderLayout.NORTH);
575 pnl_idInputBL.add(pnl_idInput, BorderLayout.CENTER);
577 final String foundStructureSummary = MessageManager
578 .getString("label.found_structures_summary");
579 final String configureCols = MessageManager
580 .getString("label.configure_displayed_columns");
581 ChangeListener changeListener = new ChangeListener()
584 public void stateChanged(ChangeEvent changeEvent)
586 JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent
588 int index = sourceTabbedPane.getSelectedIndex();
589 btn_add.setVisible(targetView.isVisible());
590 btn_newView.setVisible(true);
591 btn_cancel.setVisible(true);
592 if (sourceTabbedPane.getTitleAt(index).equals(configureCols))
594 btn_add.setEnabled(false);
595 btn_cancel.setEnabled(false);
596 btn_add.setVisible(false);
597 btn_newView.setEnabled(false);
598 btn_cancel.setVisible(false);
599 previousWantedFields = pdbDocFieldPrefs
600 .getStructureSummaryFields()
601 .toArray(new FTSDataColumnI[0]);
603 if (sourceTabbedPane.getTitleAt(index)
604 .equals(foundStructureSummary))
606 btn_cancel.setEnabled(true);
607 if (wantedFieldsUpdated())
613 validateSelections();
618 pnl_filter.addChangeListener(changeListener);
619 pnl_filter.setPreferredSize(new Dimension(width, height));
620 pnl_filter.add(foundStructureSummary, scrl_foundStructures);
621 pnl_filter.add(configureCols, pdbDocFieldPrefs);
623 JPanel pnl_locPDB = new JPanel(new BorderLayout());
624 pnl_locPDB.add(scrl_localPDB);
626 pnl_switchableViews.add(pnl_fileChooserBL, VIEWS_FROM_FILE);
627 pnl_switchableViews.add(pnl_idInputBL, VIEWS_ENTER_ID);
628 pnl_switchableViews.add(pnl_filter, VIEWS_FILTER);
629 pnl_switchableViews.add(pnl_locPDB, VIEWS_LOCAL_PDB);
631 this.setLayout(new BorderLayout());
632 this.add(pnl_main, java.awt.BorderLayout.NORTH);
633 this.add(pnl_switchableViews, java.awt.BorderLayout.CENTER);
634 // this.add(pnl_actions, java.awt.BorderLayout.SOUTH);
635 statusPanel.setLayout(new GridLayout());
637 JPanel pnl_actionsAndStatus = new JPanel(new BorderLayout());
638 pnl_actionsAndStatus.add(actionsPanel, BorderLayout.CENTER);
639 pnl_actionsAndStatus.add(statusPanel, BorderLayout.SOUTH);
640 statusPanel.add(statusBar, null);
641 this.add(pnl_actionsAndStatus, java.awt.BorderLayout.SOUTH);
643 mainFrame.addInternalFrameListener(
644 new javax.swing.event.InternalFrameAdapter()
647 public void internalFrameClosing(InternalFrameEvent e)
649 closeAction(pnl_filter.getHeight());
652 mainFrame.setVisible(true);
653 mainFrame.setContentPane(this);
654 mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
655 Integer x = tempUserPrefs.get("structureChooser.x");
656 Integer y = tempUserPrefs.get("structureChooser.y");
657 if (x != null && y != null)
659 mainFrame.setLocation(x, y);
661 Desktop.addInternalFrame(mainFrame, frameTitle, width, height);
665 protected void closeAction(int preferredHeight)
667 // System.out.println(">>>>>>>>>> closing internal frame!!!");
668 // System.out.println("width : " + mainFrame.getWidth());
669 // System.out.println("heigh : " + mainFrame.getHeight());
670 // System.out.println("x : " + mainFrame.getX());
671 // System.out.println("y : " + mainFrame.getY());
672 tempUserPrefs.put("structureChooser.width", pnl_filter.getWidth());
673 tempUserPrefs.put("structureChooser.height", preferredHeight);
674 tempUserPrefs.put("structureChooser.x", mainFrame.getX());
675 tempUserPrefs.put("structureChooser.y", mainFrame.getY());
679 public boolean wantedFieldsUpdated()
681 if (previousWantedFields == null)
686 FTSDataColumnI[] currentWantedFields = pdbDocFieldPrefs
687 .getStructureSummaryFields().toArray(new FTSDataColumnI[0]);
688 return Arrays.equals(currentWantedFields, previousWantedFields) ? false
695 * Event listener for the 'filter' combo-box and 'invert' check-box
697 public void itemStateChanged(ItemEvent e)
703 * This inner class provides the data model for the structure filter combo-box
708 public class FilterOption
712 private String value;
716 private boolean addSeparatorAfter;
719 * Model for structure filter option
722 * - the name of the Option
724 * - the value of the option
726 * - the category of the filter option
727 * @param addSeparatorAfter
728 * - if true, a horizontal separator is rendered immediately after
729 * this filter option, otherwise
731 public FilterOption(String name, String value, String view,
732 boolean addSeparatorAfter)
737 this.addSeparatorAfter = addSeparatorAfter;
740 public String getName()
745 public void setName(String name)
750 public String getValue()
755 public void setValue(String value)
760 public String getView()
765 public void setView(String view)
771 public String toString()
776 public boolean isAddSeparatorAfter()
778 return addSeparatorAfter;
781 public void setAddSeparatorAfter(boolean addSeparatorAfter)
783 this.addSeparatorAfter = addSeparatorAfter;
788 * This inner class provides the provides the data model for associate
789 * sequence combo-box - cmb_assSeq
794 public class AssociateSeqOptions
796 private SequenceI sequence;
800 public AssociateSeqOptions(SequenceI seq)
803 this.name = (seq.getName().length() >= 23)
804 ? seq.getName().substring(0, 23)
808 public AssociateSeqOptions(String name, SequenceI seq)
815 public String toString()
820 public String getName()
825 public void setName(String name)
830 public SequenceI getSequence()
835 public void setSequence(SequenceI sequence)
837 this.sequence = sequence;
843 * This inner class holds the Layout and configuration of the panel which
844 * handles association of manually fetched structures to a unique sequence
845 * when more than one sequence selection is made
850 public class AssociateSeqPanel extends JPanel implements ItemListener
852 private JComboBox<AssociateSeqOptions> cmb_assSeq = new JComboBox<>();
854 private JLabel lbl_associateSeq = new JLabel();
856 public AssociateSeqPanel()
858 this.setLayout(new FlowLayout());
859 this.add(cmb_assSeq);
860 this.add(lbl_associateSeq);
861 cmb_assSeq.setToolTipText(
862 MessageManager.getString("info.associate_wit_sequence"));
863 cmb_assSeq.addItemListener(this);
866 public void loadCmbAssSeq()
868 populateCmbAssociateSeqOptions(cmb_assSeq, lbl_associateSeq);
871 public JComboBox<AssociateSeqOptions> getCmb_assSeq()
876 public void setCmb_assSeq(JComboBox<AssociateSeqOptions> cmb_assSeq)
878 this.cmb_assSeq = cmb_assSeq;
882 public void itemStateChanged(ItemEvent e)
884 if (e.getStateChange() == ItemEvent.SELECTED)
886 cmbAssSeqStateChanged();
891 public JTable getResultTable()
896 public JComboBox<FilterOption> getCmbFilterOption()
898 return cmb_filterOption;
902 * Custom ListCellRenderer for adding a separator between different categories
903 * of structure chooser filter option drop-down.
908 public abstract class CustomComboSeparatorsRenderer
909 implements ListCellRenderer<Object>
911 private ListCellRenderer<Object> regent;
913 private JPanel separatorPanel = new JPanel(new BorderLayout());
915 private JSeparator jSeparator = new JSeparator();
917 public CustomComboSeparatorsRenderer(
918 ListCellRenderer<Object> listCellRenderer)
920 this.regent = listCellRenderer;
924 public Component getListCellRendererComponent(JList list, Object value,
925 int index, boolean isSelected, boolean cellHasFocus)
928 Component comp = regent.getListCellRendererComponent(list, value,
929 index, isSelected, cellHasFocus);
931 && addSeparatorAfter(list, (FilterOption) value, index))
933 separatorPanel.removeAll();
934 separatorPanel.add(comp, BorderLayout.CENTER);
935 separatorPanel.add(jSeparator, BorderLayout.SOUTH);
936 return separatorPanel;
944 protected abstract boolean addSeparatorAfter(JList list,
945 FilterOption value, int index);
948 protected abstract void stateChanged(ItemEvent e);
950 protected abstract void add_ActionPerformed();
952 protected abstract void newView_ActionPerformed();
954 protected abstract void pdbFromFile_actionPerformed();
956 protected abstract void txt_search_ActionPerformed();
958 protected abstract void populateCmbAssociateSeqOptions(
959 JComboBox<AssociateSeqOptions> cmb_assSeq,
960 JLabel lbl_associateSeq);
962 protected abstract void cmbAssSeqStateChanged();
964 protected abstract void tabRefresh();
966 protected abstract void validateSelections();