View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.ui;
39  
40  import ffx.utilities.Keyword;
41  
42  import java.awt.Color;
43  import java.awt.Component;
44  import java.awt.Dimension;
45  import java.awt.FlowLayout;
46  import java.awt.GridBagConstraints;
47  import java.awt.GridBagLayout;
48  import java.awt.Point;
49  import java.awt.event.ActionEvent;
50  import java.awt.event.ActionListener;
51  import java.awt.event.MouseEvent;
52  import java.awt.event.MouseListener;
53  import java.util.ArrayList;
54  import java.util.Objects;
55  import java.util.logging.Level;
56  import java.util.logging.Logger;
57  import javax.swing.JButton;
58  import javax.swing.JCheckBox;
59  import javax.swing.JComboBox;
60  import javax.swing.JLabel;
61  import javax.swing.JPanel;
62  import javax.swing.JTextArea;
63  import javax.swing.JTextField;
64  import javax.swing.JViewport;
65  import javax.swing.border.EmptyBorder;
66  import javax.swing.event.ChangeEvent;
67  import javax.swing.event.ChangeListener;
68  import javax.swing.event.DocumentEvent;
69  import javax.swing.event.DocumentListener;
70  
71  /**
72   * The KeywordComponent class is used to represent one TINKER keyword.
73   *
74   * @author Michael J. Schnieders
75   */
76  @SuppressWarnings("unchecked")
77  public final class KeywordComponent
78      implements MouseListener, ActionListener, ChangeListener, DocumentListener {
79  
80    private static final Logger logger = Logger.getLogger(KeywordComponent.class.getName());
81    /**
82     * This is used to test if ANY keyword has been modified, so it is static across all keyword
83     * objects
84     */
85    private static boolean isModified = false;
86  
87    private static final Dimension labelDimension;
88    private static final Dimension entryDimension;
89  
90    static {
91      JTextField textField = new JTextField(20);
92      labelDimension = textField.getPreferredSize();
93      textField = new JTextField(25);
94      entryDimension = textField.getPreferredSize();
95    }
96  
97    private final FlowLayout flowLayout;
98    /**
99     * TINKER Keyword String.
100    */
101   private final String keyword;
102   /**
103    * TINKER Keyword Group.
104    */
105   private final String keywordGroup;
106   /**
107    * An ArrayList of Components used to represent this Keyword
108    */
109   private final ArrayList<Component> keywordValues;
110 
111   private JPanel keywordGUI = null;
112   /**
113    * The type of Swing Conponent used in representing this Keyword
114    */
115   private final SwingRepresentation swingRepresentation;
116 
117   private String[] options;
118   private final String keywordDescription;
119   private final JTextArea output;
120   private boolean active;
121   private boolean init = false;
122 
123   /**
124    * The Default Constructor k - Keyword String kg - Keyword Group t - Type of GUI Components used
125    * to represent Keyword modifiers d - Keyword description
126    *
127    * @param keyword             a {@link java.lang.String} object.
128    * @param keywordGroup        a {@link java.lang.String} object.
129    * @param swingRepresentation a {@link ffx.ui.KeywordComponent.SwingRepresentation} object.
130    * @param keywordDescription  a {@link java.lang.String} object.
131    * @param jTextArea           a {@link javax.swing.JTextArea} object.
132    */
133   KeywordComponent(
134       String keyword,
135       String keywordGroup,
136       SwingRepresentation swingRepresentation,
137       String keywordDescription,
138       JTextArea jTextArea) {
139     flowLayout = new FlowLayout(FlowLayout.LEFT, 5, 5);
140     this.keyword = keyword;
141     this.keywordGroup = keywordGroup;
142     keywordValues = new ArrayList<>();
143     this.swingRepresentation = swingRepresentation;
144     this.keywordDescription = keywordDescription;
145     output = jTextArea;
146     active = false;
147     flowLayout.setHgap(2);
148     flowLayout.setVgap(1);
149   }
150 
151   /**
152    * Constructor for KeywordComponent.
153    *
154    * @param keyword             a {@link java.lang.String} object.
155    * @param keywordGroup        a {@link java.lang.String} object.
156    * @param swingRepresentation a {@link ffx.ui.KeywordComponent.SwingRepresentation} object.
157    * @param keywordDescription  a {@link java.lang.String} object.
158    * @param jTextArea           a {@link javax.swing.JTextArea} object.
159    * @param options             an array of {@link java.lang.String} objects.
160    */
161   KeywordComponent(
162       String keyword,
163       String keywordGroup,
164       SwingRepresentation swingRepresentation,
165       String keywordDescription,
166       JTextArea jTextArea,
167       String[] options) {
168     this(keyword, keywordGroup, swingRepresentation, keywordDescription, jTextArea);
169     this.options = options;
170   }
171 
172   /**
173    * fillPanel
174    *
175    * @param jPanel             a {@link javax.swing.JPanel} object.
176    * @param gridBagLayout      a {@link java.awt.GridBagLayout} object.
177    * @param gridBagConstraints a {@link java.awt.GridBagConstraints} object.
178    */
179   static void fillPanel(
180       JPanel jPanel, GridBagLayout gridBagLayout, GridBagConstraints gridBagConstraints) {
181     JLabel jfill = new JLabel(" ");
182     gridBagConstraints.weightx = 1;
183     gridBagConstraints.weighty = 1;
184     gridBagConstraints.fill = GridBagConstraints.BOTH;
185     gridBagConstraints.gridwidth = GridBagConstraints.REMAINDER;
186     gridBagConstraints.gridheight = GridBagConstraints.REMAINDER;
187     gridBagLayout.setConstraints(jfill, gridBagConstraints);
188     jPanel.add(jfill);
189   }
190 
191   /**
192    * isKeywordModified
193    *
194    * @return a boolean.
195    */
196   static boolean isKeywordModified() {
197     return isModified;
198   }
199 
200   /**
201    * setKeywordModified
202    *
203    * @param b a boolean.
204    */
205   static void setKeywordModified(boolean b) {
206     isModified = b;
207   }
208 
209   /**
210    * {@inheritDoc}
211    */
212   @Override
213   @SuppressWarnings("unchecked")
214   public void actionPerformed(ActionEvent evt) {
215     synchronized (this) {
216       isModified = true;
217       if (evt.getSource() instanceof JButton) {
218         JButton button = (JButton) evt.getSource();
219         if (button.getText().equalsIgnoreCase("Add")) {
220           JTextField text = (JTextField) keywordValues.get(3);
221           String s = text.getText();
222           if (s != null && !s.trim().isEmpty()) {
223             JComboBox<String> jcb = (JComboBox<String>) keywordValues.get(1);
224             jcb.addItem(s);
225             text.setText("");
226             active = true;
227           }
228         } else if (button.getText().equalsIgnoreCase("Remove")) {
229           JComboBox<String> jcb = (JComboBox<String>) keywordValues.get(1);
230           if (jcb.getItemCount() > 0) {
231             jcb.removeItemAt(jcb.getSelectedIndex());
232           }
233           if (jcb.getItemCount() == 0) {
234             active = false;
235           }
236         }
237       } else if (evt.getSource() instanceof JComboBox) {
238         JComboBox<String> jcb = (JComboBox<String>) evt.getSource();
239         String selected = (String) jcb.getSelectedItem();
240         if (selected == null) {
241           active = false;
242         } else {
243           active = !selected.equalsIgnoreCase("DEFAULT");
244         }
245       }
246     }
247   }
248 
249   /**
250    * {@inheritDoc}
251    */
252   @Override
253   public void changedUpdate(DocumentEvent evt) {
254     isModified = true;
255   }
256 
257   /**
258    * {@inheritDoc}
259    *
260    * <p>Overidden equals method return true if object equals this, or if it of the same class and
261    * has the same Tinker Keyword.
262    */
263   @Override
264   public boolean equals(Object object) {
265     if (this == object) {
266       return true;
267     } else if (object == null || getClass() != object.getClass()) {
268       return false;
269     }
270     KeywordComponent other = (KeywordComponent) object;
271     return keyword.equalsIgnoreCase(other.keyword);
272   }
273 
274   /**
275    * Getter for the field <code>keyword</code>.
276    *
277    * @return a {@link java.lang.String} object.
278    */
279   public String getKeyword() {
280     return keyword;
281   }
282 
283   /**
284    * {@inheritDoc}
285    */
286   @Override
287   public int hashCode() {
288     return Objects.hash(keyword.hashCode());
289   }
290 
291   /**
292    * {@inheritDoc}
293    */
294   @Override
295   public void insertUpdate(DocumentEvent evt) {
296     isModified = true;
297   }
298 
299   /**
300    * isActive
301    *
302    * @return a boolean.
303    */
304   public boolean isActive() {
305     return active;
306   }
307 
308   /**
309    * {@inheritDoc}
310    */
311   @Override
312   public void mouseClicked(MouseEvent evt) {
313     synchronized (this) {
314       active = toString() != null;
315       output.setText(keyword + ": " + keywordDescription);
316       JViewport jsp = (JViewport) output.getParent();
317       jsp.setViewPosition(new Point(20, 20));
318     }
319   }
320 
321   /**
322    * {@inheritDoc}
323    */
324   @Override
325   public void mouseEntered(MouseEvent evt) {
326     mouseClicked(evt);
327   }
328 
329   /**
330    * {@inheritDoc}
331    */
332   @Override
333   public void mouseExited(MouseEvent evt) {
334     mouseClicked(evt);
335   }
336 
337   /**
338    * {@inheritDoc}
339    */
340   @Override
341   public void mousePressed(MouseEvent evt) {
342   }
343 
344   /**
345    * {@inheritDoc}
346    */
347   @Override
348   public void mouseReleased(MouseEvent evt) {
349   }
350 
351   /**
352    * {@inheritDoc}
353    */
354   @Override
355   public void removeUpdate(DocumentEvent evt) {
356     isModified = true;
357   }
358 
359   /**
360    * {@inheritDoc}
361    */
362   @Override
363   public void stateChanged(ChangeEvent evt) {
364     isModified = true;
365   }
366 
367   /**
368    * {@inheritDoc}
369    *
370    * <p>Overridden toString methods facilitates Keyword output to a file.
371    */
372   @Override
373   public String toString() {
374     synchronized (this) {
375       if (!active || !init) {
376         return null;
377       }
378       StringBuilder s;
379       // Torsion entries are long...
380       // Some static layout variables
381       String spaces = "                             ";
382       if (!keyword.equalsIgnoreCase("TORSION")) {
383         s = new StringBuilder(keyword + spaces.substring(0, 18 - keyword.length()));
384       } else {
385         s = new StringBuilder(keyword);
386       }
387       for (Component c : keywordValues) {
388         if (c instanceof JCheckBox) {
389           JCheckBox cb = (JCheckBox) c;
390           if (keywordValues.size() == 1) {
391             if (!cb.isSelected()) {
392               return null;
393             }
394           } else if (cb.getText().equalsIgnoreCase(keyword)) {
395             if (!cb.isSelected()) {
396               s = new StringBuilder();
397             }
398           } else {
399             if (cb.isSelected()) {
400               if (!s.isEmpty()) {
401                 s.append("\n").append(keyword).append(" ").append(cb.getText());
402               } else {
403                 s.append(keyword).append(" ").append(cb.getText());
404               }
405             }
406           }
407         } else if (c instanceof JTextField) {
408           JTextField tf = (JTextField) c;
409           if (tf.getText().isEmpty()) {
410             return null;
411           }
412           String v = tf.getText();
413           if (v.length() < 8) {
414             s.append(v).append(spaces, 0, 8 - v.length());
415           } else {
416             s.append(v);
417           }
418           break;
419         } else if (c instanceof JComboBox) {
420           JComboBox<String> cb = (JComboBox<String>) c;
421           if (swingRepresentation == SwingRepresentation.EDITCOMBOBOX) {
422             int count = cb.getItemCount();
423             if (count == 0) {
424               return null;
425             }
426             String[] entries = new String[count];
427             for (int i = 0; i < count; i++) {
428               entries[i] = cb.getItemAt(i);
429             }
430             java.util.Arrays.sort(entries);
431             StringBuilder sb = new StringBuilder();
432             for (int i = 0; i < count; i++) {
433               sb.append(keyword).append(spaces.substring(0, 18 - keyword.length()));
434               sb.append(entries[i].toUpperCase());
435               if (i < count - 1) {
436                 sb.append("\n");
437               }
438             }
439             s = sb;
440           } else {
441             String selection = (String) cb.getSelectedItem();
442             if (selection.startsWith("DEFAULT")) {
443               return null;
444             } else if (!selection.equalsIgnoreCase("PRESENT")) {
445               s.append(" ").append(selection);
446             }
447           }
448           break;
449         }
450       }
451       if (s.isEmpty()) {
452         return null;
453       }
454       return s.toString();
455     }
456   }
457 
458   private void checkBoxesInit() {
459     checkBoxInit();
460     for (String s : options) {
461       checkBoxInit(s);
462     }
463   }
464 
465   private void checkBoxInit() {
466     JCheckBox cb = new JCheckBox(keyword, false);
467     cb.setPreferredSize(labelDimension);
468     cb.setMaximumSize(labelDimension);
469     cb.addMouseListener(this);
470     cb.addChangeListener(this);
471     keywordValues.add(cb);
472   }
473 
474   private void checkBoxInit(String label) {
475     JCheckBox cb = new JCheckBox(label, false);
476     cb.addMouseListener(this);
477     cb.addChangeListener(this);
478     keywordValues.add(cb);
479   }
480 
481   /**
482    * clearKeywordComponent
483    */
484   void clearKeywordComponent() {
485     synchronized (this) {
486       active = false;
487       if (!init) {
488         return;
489       }
490       if (swingRepresentation == SwingRepresentation.CHECKBOX) {
491         ((JCheckBox) keywordValues.get(0)).setSelected(false);
492       } else if (swingRepresentation == SwingRepresentation.CHECKBOXES) {
493         for (Component keywordValue : keywordValues) {
494           ((JCheckBox) keywordValue).setSelected(false);
495         }
496       } else if (swingRepresentation == SwingRepresentation.COMBOBOX) {
497         JComboBox<String> jcb = (JComboBox<String>) keywordValues.get(1);
498         jcb.setSelectedItem("DEFAULT");
499       } else if (swingRepresentation == SwingRepresentation.EDITCOMBOBOX) {
500         ((JComboBox) keywordValues.get(1)).removeAllItems();
501       } else if (swingRepresentation == SwingRepresentation.TEXTFIELD) {
502         ((JTextField) keywordValues.get(1)).setText("");
503       } else {
504         logger.severe("Keyword Component: Unknown Keyword Type");
505         logger.severe("Force Field X can not continue...");
506         System.exit(-1);
507       }
508     }
509   }
510 
511   private void comboBoxInit() {
512     JLabel jl = new JLabel(keyword);
513     jl.addMouseListener(this);
514     jl.setPreferredSize(labelDimension);
515     jl.setMaximumSize(labelDimension);
516     keywordValues.add(jl);
517     JComboBox<String> cb = new JComboBox<>();
518     cb.setEditable(false);
519     cb.addMouseListener(this);
520     cb.addActionListener(this);
521     cb.setPreferredSize(entryDimension);
522     cb.setMaximumSize(entryDimension);
523     for (String s : options) {
524       cb.addItem(s);
525     }
526     cb.setSelectedItem("DEFAULT");
527     keywordValues.add(cb);
528   }
529 
530   private void editComboBoxInit() {
531     JLabel jl = new JLabel(keyword);
532     keywordValues.add(jl);
533     jl.addMouseListener(this);
534     jl.setPreferredSize(labelDimension);
535     jl.setMaximumSize(labelDimension);
536     JComboBox<String> cb = new JComboBox<>();
537     cb.setEditable(false);
538     cb.addActionListener(this);
539     cb.setPreferredSize(entryDimension);
540     cb.setMaximumSize(entryDimension);
541     keywordValues.add(cb);
542     JButton remove = new JButton("Remove");
543     remove.addActionListener(this);
544     keywordValues.add(remove);
545     JTextField textField = new JTextField();
546     textField.setPreferredSize(entryDimension);
547     textField.setMaximumSize(entryDimension);
548     keywordValues.add(textField);
549     JButton add = new JButton("Add");
550     add.addActionListener(this);
551     keywordValues.add(add);
552   }
553 
554   /**
555    * getKeywordData
556    *
557    * @param keywordData a {@link ffx.utilities.Keyword} object.
558    */
559   void getKeywordData(Keyword keywordData) {
560     synchronized (this) {
561       if (keywordData == null || !active) {
562         return;
563       }
564       for (Component c : keywordValues) {
565         if (c instanceof JTextField) {
566           JTextField tf = (JTextField) c;
567           if (!tf.getText().isEmpty()) {
568             keywordData.append(tf.getText());
569           }
570           break;
571         } else if (c instanceof JComboBox) {
572           JComboBox<String> cb = (JComboBox<String>) c;
573           if (swingRepresentation == SwingRepresentation.COMBOBOX) {
574             String s = (String) cb.getSelectedItem();
575             if ("DEFAULT".equals(s)) {
576               logger.log(Level.WARNING, "Keyword should not be active: {0}", toString());
577               return;
578             }
579             keywordData.append(s);
580           } else {
581             int num = cb.getItemCount();
582             for (int i = 0; i < num; i++) {
583               String s = cb.getItemAt(i);
584               keywordData.append(s);
585             }
586           }
587           break;
588         } else if (c instanceof JCheckBox) {
589           JCheckBox cb = (JCheckBox) c;
590           String text = cb.getText();
591           if (cb.isSelected()) {
592             keywordData.append(text);
593           }
594         }
595       }
596     }
597   }
598 
599   /**
600    * Getter for the field <code>keywordDescription</code>.
601    *
602    * @return a {@link java.lang.String} object.
603    */
604   String getKeywordDescription() {
605     return keywordDescription;
606   }
607 
608   /**
609    * Getter for the field <code>keywordGroup</code>.
610    *
611    * @return a {@link java.lang.String} object.
612    */
613   String getKeywordGroup() {
614     return keywordGroup;
615   }
616 
617   /**
618    * Returns a JPanel with a GridLayout LayoutManager that contains a Swing representation of the
619    * Keyword and Modifiers in a single row.
620    *
621    * @return a {@link javax.swing.JPanel} object.
622    */
623   JPanel getKeywordGUI() {
624     synchronized (this) {
625       if (keywordGUI == null) {
626         if (!init) {
627           initSwingComponents();
628         }
629         if (swingRepresentation == SwingRepresentation.MULTIPOLE) {
630           keywordGUI.add(keywordValues.get(0));
631         } else {
632           keywordGUI = new JPanel(flowLayout);
633           for (Component c : keywordValues) {
634             keywordGUI.add(c);
635           }
636         }
637       }
638       return keywordGUI;
639     }
640   }
641 
642   private void initSwingComponents() {
643     if (swingRepresentation == SwingRepresentation.CHECKBOX) {
644       checkBoxInit();
645     } else if (swingRepresentation == SwingRepresentation.CHECKBOXES) {
646       checkBoxesInit();
647     } else if (swingRepresentation == SwingRepresentation.COMBOBOX) {
648       comboBoxInit();
649     } else if (swingRepresentation == SwingRepresentation.EDITCOMBOBOX) {
650       editComboBoxInit();
651     } else if (swingRepresentation == SwingRepresentation.TEXTFIELD) {
652       textFieldInit();
653     } else {
654       return;
655     }
656     init = true;
657   }
658 
659   /**
660    * Load a single line Keyword entry into this KeywordComponent. Keywords that can be repeated
661    * multipule times are ComboBoxes are stored in ComboBoxes.
662    *
663    * @param s A Keyword line, not including the Keyword itself.
664    */
665   @SuppressWarnings("unchecked")
666   void loadKeywordEntry(String s) {
667     synchronized (this) {
668       if (!init) {
669         initSwingComponents();
670       }
671       for (Component c : keywordValues) {
672         if (c instanceof JCheckBox) {
673           JCheckBox cb = (JCheckBox) c;
674           if (s != null && s.equalsIgnoreCase(cb.getText())) {
675             cb.setSelected(true);
676           } else if (s == null) {
677             cb.setSelected(false);
678           }
679         } else if (c instanceof JTextField) {
680           JTextField tf = (JTextField) c;
681           tf.setText(s);
682           break;
683         } else if (c instanceof JComboBox && s != null) {
684           JComboBox<String> cb = (JComboBox<String>) c;
685           if (swingRepresentation == SwingRepresentation.EDITCOMBOBOX) {
686             cb.addItem(s);
687           } else {
688             cb.setSelectedItem(s);
689           }
690           break;
691         }
692       }
693       active = toString() != null;
694     }
695   }
696 
697   private void textFieldInit() {
698     JLabel jl = new JLabel(keyword);
699     jl.addMouseListener(this);
700     jl.setPreferredSize(labelDimension);
701     jl.setMaximumSize(labelDimension);
702     keywordValues.add(jl);
703     JTextField tf;
704     tf = new JTextField("");
705     tf.setColumns(25);
706     tf.setActionCommand(keyword);
707     tf.addMouseListener(this);
708     tf.getDocument().addDocumentListener(this);
709     if (keyword.equalsIgnoreCase("PARAMETERS") || keyword.equalsIgnoreCase("FORCEFIELD")) {
710       tf.setEditable(false);
711       EmptyBorder b = new EmptyBorder(1, 1, 1, 1);
712       tf.setBorder(b);
713       tf.setForeground(Color.BLUE);
714     }
715     tf.setPreferredSize(entryDimension);
716     tf.setMaximumSize(entryDimension);
717     keywordValues.add(tf);
718   }
719 
720   public enum SwingRepresentation {
721     TEXTFIELD,
722     CHECKBOX,
723     CHECKBOXES,
724     EDITCOMBOBOX,
725     COMBOBOX,
726     MULTIPOLE
727   }
728 }