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-2023.
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 java.awt.BorderLayout;
41  import java.awt.FlowLayout;
42  import java.awt.Font;
43  import java.awt.Insets;
44  import java.awt.event.ActionEvent;
45  import java.awt.event.ActionListener;
46  import java.io.*;
47  import java.util.Hashtable;
48  import java.util.Vector;
49  import java.util.logging.Logger;
50  import javax.swing.BorderFactory;
51  import javax.swing.ImageIcon;
52  import javax.swing.JButton;
53  import javax.swing.JFileChooser;
54  import javax.swing.JLabel;
55  import javax.swing.JPanel;
56  import javax.swing.JProgressBar;
57  import javax.swing.JScrollPane;
58  import javax.swing.JTabbedPane;
59  import javax.swing.JTextArea;
60  import javax.swing.JToolBar;
61  import javax.swing.border.Border;
62  import javax.swing.border.EtchedBorder;
63  
64  /**
65   * The LogPanel is a *very* simple editor that displays log files created by TINKER jobs. Any text
66   * file can be edited.
67   *
68   * @author Michael J. Schnieders
69   */
70  public class LogPanel extends JPanel implements ActionListener {
71  
72    @Serial
73    private static final long serialVersionUID = 1L;
74  
75    private static final Logger logger = Logger.getLogger(LogPanel.class.getName());
76    private Vector<Thread> tinkerThreads;
77    // A Hashtable of JTextAreas labeled by absolute path to the Log File each
78    // displays
79    private Hashtable<String, JTextArea> logFiles = new Hashtable<String, JTextArea>();
80    // LogPanel GUI Components
81    private JToolBar toolBar;
82    private JTabbedPane resultsTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
83    private JProgressBar statusProgressBar;
84    private Font font;
85    private JPanel noLogsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));
86    private EtchedBorder eb = new EtchedBorder(EtchedBorder.RAISED);
87  
88    /**
89     * Constructor
90     *
91     * @param f a {@link ffx.ui.MainPanel} object.
92     */
93    public LogPanel(MainPanel f) {
94      tinkerThreads = null; // mainPanel.getModelingPanel().getModelingJobs();
95      initToolBar();
96      setLayout(new BorderLayout());
97      Border eb = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
98      statusProgressBar = new JProgressBar();
99      statusProgressBar.setBorder(eb);
100     statusProgressBar.setStringPainted(true);
101     ClassLoader loader = getClass().getClassLoader();
102     ImageIcon icon = new ImageIcon(loader.getResource("ffx/ui/icons/page_code.png"));
103     JLabel noLogsLabel = new JLabel("TINKER logs are displayed here.");
104     noLogsLabel.setIcon(icon);
105     noLogsPanel.add(noLogsLabel);
106     noLogsPanel.setBorder(eb);
107     JLabel status = new JLabel("  ");
108     status.setBorder(eb);
109     add(toolBar, BorderLayout.NORTH);
110     add(noLogsPanel, BorderLayout.CENTER);
111     add(status, BorderLayout.SOUTH);
112     font = Font.decode("Monospaced");
113     refreshStatus();
114   }
115 
116   /** {@inheritDoc} */
117   @Override
118   public void actionPerformed(ActionEvent evt) {
119     if (evt.getSource() instanceof javax.swing.Timer) {
120       if (resultsTabbedPane.getTabCount() > 0) {
121         refresh();
122       }
123       return;
124     }
125     String arg = evt.getActionCommand();
126     if (arg == null) {
127       return;
128     }
129     if (arg.equalsIgnoreCase("Refresh")) {
130       refresh();
131     } else if (arg.equalsIgnoreCase("Close")) {
132       close();
133     } else if (arg.equalsIgnoreCase("Close All")) {
134       closeAll();
135     } else if (arg.equalsIgnoreCase("Open...")) {
136       JFileChooser d = MainPanel.resetFileChooser();
137       d.setAcceptAllFileFilterUsed(true);
138       d.setDialogTitle("Open Log File");
139       int result = d.showOpenDialog(this);
140       if (result == JFileChooser.APPROVE_OPTION) {
141         File f = d.getSelectedFile();
142         addPane(f);
143       }
144     } else if (arg.equalsIgnoreCase("Save")) {
145       saveSelected();
146     } else if (arg.equalsIgnoreCase("Save As...")) {
147       saveSelectedAs();
148     }
149   }
150 
151   /** close */
152   public void close() {
153     int index = resultsTabbedPane.getSelectedIndex();
154     if (index >= 0) {
155       String title = resultsTabbedPane.getTitleAt(index);
156       resultsTabbedPane.remove(index);
157       logFiles.remove(title);
158       for (Thread t : tinkerThreads) {
159         String name = t.getName();
160         if (name.equals(title)) {
161           tinkerThreads.remove(t);
162           break;
163         }
164       }
165       if (resultsTabbedPane.getComponentCount() == 0) {
166         remove(resultsTabbedPane);
167         add(noLogsPanel, BorderLayout.CENTER);
168       }
169       validate();
170       repaint();
171     }
172   }
173 
174   /**
175    * close
176    *
177    * @param file a {@link java.lang.String} object.
178    */
179   public void close(String file) {
180     synchronized (this) {
181       int index = -1;
182       for (int i = 0; i < resultsTabbedPane.getTabCount(); i++) {
183         String title = resultsTabbedPane.getTitleAt(i);
184         if (file.equals(title)) {
185           index = i;
186           break;
187         }
188       }
189       if (index < 0) {
190         return;
191       }
192       String title = resultsTabbedPane.getTitleAt(index);
193       resultsTabbedPane.remove(index);
194       logFiles.remove(title);
195       for (Thread t : tinkerThreads) {
196         String name = t.getName();
197         if (name.equals(title)) {
198           tinkerThreads.remove(t);
199           break;
200         }
201       }
202       if (resultsTabbedPane.getComponentCount() == 0) {
203         remove(resultsTabbedPane);
204         add(noLogsPanel, BorderLayout.CENTER);
205       }
206       validate();
207       repaint();
208     }
209   }
210 
211   /**
212    * getProgressBar
213    *
214    * @return a {@link javax.swing.JProgressBar} object.
215    */
216   public JProgressBar getProgressBar() {
217     return statusProgressBar;
218   }
219 
220   /** selected */
221   public void selected() {
222     validate();
223     repaint();
224   }
225 
226   /**
227    * setDone
228    *
229    * @param logFileName a {@link java.lang.String} object.
230    */
231   public void setDone(String logFileName) {
232     synchronized (this) {
233       if (logFileName == null) {
234         return;
235       }
236       File logFile = new File(logFileName);
237       for (Thread thread : tinkerThreads) {
238         String jobName = thread.getName();
239         if (jobName.equals(logFileName)) {
240           tinkerThreads.remove(thread);
241           break;
242         }
243       }
244       if (!logFile.exists()) {
245         return;
246       }
247       if (logFiles.containsKey(logFile.getAbsolutePath())) {
248         JTextArea logTextArea = logFiles.get(logFile.getAbsolutePath());
249         loadText(logTextArea, logFile);
250       } // Create a new TextArea for the file
251       else {
252         addPane(logFile);
253       }
254       resultsTabbedPane.setSelectedIndex(resultsTabbedPane.indexOfTab(logFile.getAbsolutePath()));
255       refreshStatus();
256     }
257   }
258 
259   /**
260    * toString
261    *
262    * @return a {@link java.lang.String} object.
263    */
264   public String toString() {
265     return "Logging";
266   }
267 
268   private void addPane(File logFile) {
269     if (!logFile.exists()) {
270       return;
271     }
272     JTextArea logTextArea = new JTextArea();
273     logTextArea.setEditable(logFile.canWrite());
274     logTextArea.setFont(font);
275     logFiles.put(logFile.getAbsolutePath(), logTextArea);
276     JScrollPane scrollPane =
277         new JScrollPane(
278             logTextArea,
279             JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
280             JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
281     scrollPane.setBorder(eb);
282     resultsTabbedPane.add(scrollPane, logFile.getAbsolutePath());
283     resultsTabbedPane.setSelectedIndex(resultsTabbedPane.getComponentCount() - 1);
284     if (resultsTabbedPane.getComponentCount() == 1) {
285       remove(noLogsPanel);
286       add(resultsTabbedPane, BorderLayout.CENTER);
287       validate();
288       repaint();
289     }
290     loadText(logTextArea, logFile);
291   }
292 
293   /** closeAll */
294   private void closeAll() {
295     synchronized (this) {
296       resultsTabbedPane.removeAll();
297       logFiles.clear();
298       tinkerThreads.clear();
299     }
300   }
301 
302   private void initToolBar() {
303     toolBar = new JToolBar("Results");
304     toolBar.setLayout(new FlowLayout(FlowLayout.LEFT));
305     JButton jbrefresh =
306         new JButton(
307             new ImageIcon(
308                 getClass().getClassLoader().getResource("ffx/ui/icons/page_refresh.png")));
309     jbrefresh.setActionCommand("Refresh");
310     jbrefresh.setToolTipText("Refresh the Logs Panel");
311     jbrefresh.addActionListener(this);
312     Insets insets = jbrefresh.getInsets();
313     insets.top = 2;
314     insets.bottom = 2;
315     insets.left = 2;
316     insets.right = 2;
317     jbrefresh.setMargin(insets);
318     toolBar.add(jbrefresh);
319     toolBar.addSeparator();
320     JButton jbopen =
321         new JButton(
322             new ImageIcon(getClass().getClassLoader().getResource("ffx/ui/icons/page_open.png")));
323     jbopen.setActionCommand("Open...");
324     jbopen.setToolTipText("Open any Text File");
325     jbopen.addActionListener(this);
326     jbopen.setMargin(insets);
327     toolBar.add(jbopen);
328     JButton jbsave =
329         new JButton(
330             new ImageIcon(getClass().getClassLoader().getResource("ffx/ui/icons/disk.png")));
331     jbsave.setActionCommand("Save");
332     jbsave.setToolTipText("Save the Active File");
333     jbsave.addActionListener(this);
334     jbsave.setMargin(insets);
335     toolBar.add(jbsave);
336     JButton jbsaveas =
337         new JButton(
338             new ImageIcon(getClass().getClassLoader().getResource("ffx/ui/icons/page_save.png")));
339     jbsaveas.setActionCommand("Save As...");
340     jbsaveas.setToolTipText("Save the Active Text File Under a New Name");
341     jbsaveas.addActionListener(this);
342     jbsaveas.setMargin(insets);
343     toolBar.add(jbsaveas);
344     JButton jbclose =
345         new JButton(
346             new ImageIcon(getClass().getClassLoader().getResource("ffx/ui/icons/cancel.png")));
347     jbclose.setActionCommand("Close");
348     jbclose.setToolTipText("Close the Active Text File");
349     jbclose.addActionListener(this);
350     jbclose.setMargin(insets);
351     toolBar.add(jbclose);
352     JButton jbcloseall =
353         new JButton(
354             new ImageIcon(getClass().getClassLoader().getResource("ffx/ui/icons/stop.png")));
355     jbcloseall.setActionCommand("Close All");
356     jbcloseall.setToolTipText("Close All Open Text Files");
357     jbcloseall.addActionListener(this);
358     jbcloseall.setMargin(insets);
359     toolBar.add(jbcloseall);
360     toolBar.setFloatable(false);
361     toolBar.setRollover(true);
362     toolBar.setOrientation(JToolBar.HORIZONTAL);
363   }
364 
365   private void loadText(JTextArea logTextArea, File logFile) {
366     if (logTextArea == null || (!logFile.exists()) || (!logFile.canRead())) {
367       return;
368     }
369     logTextArea.setText("");
370     try {
371       FileInputStream inputStream = new FileInputStream(logFile);
372       BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
373       while (bufferedReader.ready()) {
374         logTextArea.append(bufferedReader.readLine() + "\n");
375       }
376       bufferedReader.close();
377       inputStream.close();
378     } catch (IOException e) {
379       logger.severe("" + e);
380     }
381     // mainPanel.setPanel(MainPanel.LOGS);
382   }
383 
384   /** refresh */
385   private void refresh() {
386     synchronized (tinkerThreads) {
387       for (Thread t : tinkerThreads) {
388         File file = new File(t.getName());
389         if (!file.exists()) {
390           continue;
391         }
392         // Refresh an already existing JTextArea
393         if (logFiles.containsKey(file.getAbsolutePath())) {
394           JTextArea ta = logFiles.get(file.getAbsolutePath());
395           loadText(ta, file);
396         } // Create a new JTextArea for the file
397         else {
398           addPane(file);
399         }
400       }
401     }
402     refreshStatus();
403     validate();
404     repaint();
405   }
406 
407   /** refreshStatus */
408   private void refreshStatus() {
409     int count = tinkerThreads.size();
410     if (count == 0) {
411       statusProgressBar.setString("");
412       statusProgressBar.setIndeterminate(false);
413     } else if (count == 1) {
414       statusProgressBar.setString("1 Job Running");
415       statusProgressBar.setIndeterminate(true);
416     } else {
417       statusProgressBar.setString(count + " Jobs Running");
418       statusProgressBar.setIndeterminate(true);
419     }
420   }
421 
422   private void saveSelected() {
423     int index = resultsTabbedPane.getSelectedIndex();
424     if (index < 0) {
425       return;
426     }
427     String title = resultsTabbedPane.getTitleAt(index);
428     JTextArea logTextArea = logFiles.get(title);
429     File logFile = new File(title);
430     if (logTextArea != null && logFile.exists() && logFile.canWrite()) {
431       try {
432         FileOutputStream outputStream = new FileOutputStream(logFile);
433         BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
434         bw.write(logTextArea.getText());
435         bw.close();
436         outputStream.close();
437       } catch (IOException e) {
438         logger.severe("" + e);
439       }
440     }
441   }
442 
443   private void saveSelectedAs() {
444     synchronized (resultsTabbedPane) {
445       int index = resultsTabbedPane.getSelectedIndex();
446       if (index < 0) {
447         return;
448       }
449       String title = resultsTabbedPane.getTitleAt(index);
450       JTextArea logTextArea = logFiles.get(title);
451       File logFile = new File(title);
452       if (logTextArea != null && logFile.exists() && logFile.canWrite()) {
453         JFileChooser fileChooser = MainPanel.resetFileChooser();
454         fileChooser.setSelectedFile(logFile);
455         fileChooser.setAcceptAllFileFilterUsed(true);
456         int result = fileChooser.showSaveDialog(this);
457         if (result == JFileChooser.APPROVE_OPTION) {
458           logFile = fileChooser.getSelectedFile();
459         } else {
460           return;
461         }
462         try {
463           logFiles.remove(title);
464           FileOutputStream outputStream = new FileOutputStream(logFile);
465           BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
466           bw.write(logTextArea.getText());
467           bw.close();
468           outputStream.close();
469           resultsTabbedPane.setTitleAt(index, logFile.getAbsolutePath());
470           logFiles.put(logFile.getAbsolutePath(), logTextArea);
471         } catch (IOException e) {
472           logger.severe("" + e);
473         }
474       }
475     }
476   }
477 }