ProcessListPane.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2013,  Michael Meyling <mime@qedeq.org>.
004  *
005  * "Hilbert II" is free software; you can redistribute
006  * it and/or modify it under the terms of the GNU General Public
007  * License as published by the Free Software Foundation; either
008  * version 2 of the License, or (at your option) any later version.
009  *
010  * This program is distributed in the hope that it will be useful,
011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013  * GNU General Public License for more details.
014  */
015 
016 package org.qedeq.gui.se.pane;
017 
018 import java.awt.Color;
019 import java.awt.Dimension;
020 import java.awt.Font;
021 import java.awt.event.ComponentAdapter;
022 import java.awt.event.ComponentEvent;
023 import java.awt.event.MouseEvent;
024 import java.lang.reflect.InvocationTargetException;
025 import java.util.Iterator;
026 import java.util.Set;
027 import java.util.TreeSet;
028 
029 import javax.swing.BorderFactory;
030 import javax.swing.Icon;
031 import javax.swing.JLabel;
032 import javax.swing.JPanel;
033 import javax.swing.JScrollPane;
034 import javax.swing.JTable;
035 import javax.swing.ListSelectionModel;
036 import javax.swing.SwingUtilities;
037 import javax.swing.event.ListSelectionEvent;
038 import javax.swing.event.ListSelectionListener;
039 import javax.swing.table.AbstractTableModel;
040 import javax.swing.table.DefaultTableCellRenderer;
041 import javax.swing.table.JTableHeader;
042 import javax.swing.table.TableColumnModel;
043 import javax.swing.text.SimpleAttributeSet;
044 import javax.swing.text.StyleConstants;
045 
046 import org.qedeq.base.io.IoUtility;
047 import org.qedeq.base.trace.Trace;
048 import org.qedeq.base.utility.DateUtility;
049 import org.qedeq.base.utility.YodaUtility;
050 import org.qedeq.gui.se.util.GuiHelper;
051 import org.qedeq.kernel.bo.common.PluginCall;
052 import org.qedeq.kernel.bo.common.QedeqBo;
053 import org.qedeq.kernel.bo.common.QedeqBoSet;
054 import org.qedeq.kernel.bo.common.ServiceProcess;
055 
056 import com.jgoodies.forms.builder.DefaultFormBuilder;
057 import com.jgoodies.forms.layout.CellConstraints;
058 import com.jgoodies.forms.layout.FormLayout;
059 import com.jgoodies.forms.layout.RowSpec;
060 
061 /**
062  * Shows QEDEQ module specific error pane.
063  *
064  @author  Michael Meyling
065  */
066 
067 public class ProcessListPane extends JPanel  {
068 
069     /** This class. */
070     private static final Class CLASS = ProcessListPane.class;
071 
072     /** Currently selected process. */
073     private final Set selectedProcesses;
074 
075     /** Start automatic refresh thread? */
076     private boolean automaticRefresh = true;
077 
078     /** Table model. */
079     private ProcessListModel model = new ProcessListModel();
080 
081     /** This table holds the error descriptions. */
082     private final JTable list = new JTable(model) {
083 
084         /**
085          * Just return the service process of the row.
086          *
087          @param  e   Mouse event.
088          @return Tool tip text.
089          */
090         public String getToolTipText(final MouseEvent e) {
091             String tip = null;
092             java.awt.Point p = e.getPoint();
093             final int row = rowAtPoint(p);
094             final int col = columnAtPoint(p);
095             try {
096                 final ServiceProcess process = model.getServiceProcess(row);
097                 switch (col) {
098                 case 0:
099                      if (process.isBlocked()) {
100                          tip = "Process is waiting";
101                      else if (process.isRunning()) {
102                          tip = "Process is running";
103                      else if (process.wasFailure()) {
104                          tip = "Process was stopped.";
105                      else if (process.wasSuccess()) {
106                          tip = "Process has finished";
107                      }
108                      break;
109                 case 1: tip = (process.getPluginCall() != null
110                      ? process.getPluginCall().getPlugin().getPluginDescription()
111                      : process.getExecutionActionDescription());
112                      break;
113                 case 2:
114                 case 3:
115                 case 4:
116                 case 5: tip = process.getQedeqUrl();
117                      break;
118                 case 7: tip = GuiHelper.getToolTipText(process.getExecutionActionDescription());
119                      break;
120                 case 8: tip = GuiHelper.getToolTipText(process.getPluginCall() != null
121                      ? process.getPluginCall().getParameterString() "");
122                      break;
123                 default: tip = "";
124                 }
125             catch (RuntimeException ex) {
126                 return super.getToolTipText(e);
127             }
128             return tip;
129         }
130 
131     };
132 
133     /** Write with this font attributes. */
134     private final SimpleAttributeSet errorAttrs = new SimpleAttributeSet();
135 
136     /** Our scroll area. */
137     private JScrollPane scrollPane;
138 
139     /**
140      * Creates new panel.
141      */
142     public ProcessListPane() {
143         super(false);
144         selectedProcesses = new TreeSet();
145         setupView();
146     }
147 
148     /**
149      * Send selection events.
150      */
151 //    private void selectLine() {
152 //        Trace.param(CLASS, this, "selectLine", "selectedLine", selectedLine);
153 //    }
154 
155     /**
156      * Assembles the GUI components of the panel.
157      */
158     private final void setupView() {
159         list.setFont(new Font("Lucida Sans Unicode", Font.PLAIN, list.getFont().getSize()));
160         list.addComponentListener(new ComponentAdapter() {
161             public void componentResized(final ComponentEvent e) {
162                 list.scrollRectToVisible(list.getCellRect(list.getRowCount() 10true));
163             }
164         });
165 
166         final FormLayout layout = new FormLayout(
167             "min:grow",
168             "0:grow");
169         final DefaultFormBuilder builder = new DefaultFormBuilder(layout, this);
170         builder.setBorder(BorderFactory.createEmptyBorder());
171         builder.setRowGroupingEnabled(true);
172 
173         final CellConstraints cc = new CellConstraints();
174         builder.appendRow(new RowSpec("0:grow"));
175 
176         list.setDefaultRenderer(Icon.class, new IconCellRenderer());
177 
178         final ListSelectionModel rowSM = list.getSelectionModel();
179         rowSM.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
180 
181         // if selection changes remember the process number (= selected row (starting with 0))
182         rowSM.addListSelectionListener(new ListSelectionListener() {
183             public void valueChanged(final ListSelectionEvent e) {
184                 if (e.getValueIsAdjusting()) {
185                     return;
186                 }
187                 selectedProcesses.clear();
188                 if (!rowSM.isSelectionEmpty()) {
189                     int minIndex = rowSM.getMinSelectionIndex();
190                     int maxIndex = rowSM.getMaxSelectionIndex();
191                     for (int i = minIndex; i <= maxIndex; i++) {
192                         if (rowSM.isSelectedIndex(i)) {
193                             final ServiceProcess process = model.getServiceProcess(i);
194                             if (process != null) {
195                                 selectedProcesses.add(process);
196                             }
197                         }
198                     }
199                 }
200             }
201         });
202 /*
203         // doing a click shall open the edit window
204         list.addMouseListener(new MouseAdapter()  {
205             public void mouseClicked(final MouseEvent e) {
206                 if (e.getClickCount() == 2) {
207                     Trace.trace(CLASS, this, "setupView$vmouseClicked", "doubleClick");
208                 }
209                 selectLine();
210             }
211         });
212 
213         // pressing the enter key shall open the edit window
214         list.getActionMap().put("selectNextRowCell", new AbstractAction() {
215             public void actionPerformed(final ActionEvent event) {
216                 Trace.param(CLASS, this, "setupView$actionPerformed", "event" , event);
217                 selectLine();
218             }
219         });
220 */
221         scrollPane = new JScrollPane(list);
222         builder.add(scrollPane,
223             cc.xywh(builder.getColumn(), builder.getRow()12"fill, fill"));
224 
225         StyleConstants.setForeground(this.errorAttrs, Color.red);
226         // because we override getScrollableTracksViewportWidth:
227         scrollPane.getViewport().setBackground(Color.white);
228         StyleConstants.setBackground(this.errorAttrs, Color.white);  // TODO mime 20080124: testing
229 
230 
231         // the default table cell renderer uses a JLabel to render the heading and we want to
232         // left align the header columns
233         // TODO mime 20080415: left align with small spaces would be better
234         final JTableHeader header = list.getTableHeader();
235         ((JLabelheader.getDefaultRenderer()).setHorizontalAlignment(JLabel.LEFT);
236         //set height of header correctly
237         header.setPreferredSize(new Dimension(list.getTableHeader().getWidth(),
238             (int) (1.1 this.getFontMetrics(getFont()).getHeight())));
239 
240         changeHeaderWidth();
241         final Thread refresh = new Thread() {
242             public void run() {
243                 while (automaticRefresh) {
244                     updateView();
245                     IoUtility.sleep(5000);  // refresh every 5 seconds
246                 }
247             }
248         };
249         refresh.setDaemon(true);
250         refresh.start();
251     }
252 
253     /**
254      * Make 2. column smaller.
255      */
256     private void changeHeaderWidth() {
257         TableColumnModel columnModel = list.getColumnModel();
258         columnModel.getColumn(0).setPreferredWidth(24);
259         columnModel.getColumn(0).setMinWidth(24);
260         columnModel.getColumn(1).setPreferredWidth(200);
261         columnModel.getColumn(1).setMinWidth(150);
262         columnModel.getColumn(2).setPreferredWidth(200);
263         columnModel.getColumn(2).setMinWidth(150);
264         columnModel.getColumn(3).setPreferredWidth(100);
265         columnModel.getColumn(3).setMinWidth(100);
266         columnModel.getColumn(4).setPreferredWidth(100);
267         columnModel.getColumn(4).setMinWidth(100);
268         columnModel.getColumn(5).setPreferredWidth(100);
269         columnModel.getColumn(5).setMinWidth(100);
270         columnModel.getColumn(6).setPreferredWidth(60);
271         columnModel.getColumn(6).setMinWidth(60);
272         columnModel.getColumn(7).setPreferredWidth(2000);
273         columnModel.getColumn(7).setMinWidth(100);
274         columnModel.getColumn(8).setPreferredWidth(200);
275         columnModel.getColumn(8).setMinWidth(100);
276     }
277 
278     /**
279      * Update from model.
280      */
281     public synchronized void updateView() {
282         final String method = "updateView";
283         Trace.begin(CLASS, this, method);
284         ((AbstractTableModellist.getModel()).fireTableDataChanged();
285         list.invalidate();
286         this.revalidate();
287 // TODO 20130228 remove me if refresh works
288 //        list.repaint();
289 //        this.repaint();
290     }
291 
292     public void refreshStates() {
293         Runnable stateChanged = new Runnable() {
294             public void run() {
295                 updateView();
296             }
297         };
298         SwingUtilities.invokeLater(stateChanged);
299     }
300 
301     /**
302      * We render our cells with this class.
303      *
304      @author  Michael Meyling
305      */
306     private static class IconCellRenderer extends DefaultTableCellRenderer {
307 
308         protected void setValue(final Object value) {
309             if (value instanceof Icon) {
310                 setIcon((Iconvalue);
311                 super.setValue(null);
312             else {
313                 setIcon(null);
314                 super.setValue(value);
315             }
316         }
317 
318     }
319 
320     public void setRunningOnly(final boolean runningOnly) {
321         model.setOnlyRunning(runningOnly);
322         selectedProcesses.clear();
323         list.getSelectionModel().clearSelection();
324         refreshStates();
325     }
326 
327     public void stopSelected() {
328         Iterator iterator = selectedProcesses.iterator();
329         while (iterator.hasNext()) {
330             final ServiceProcess process = (ServiceProcessiterator.next();
331             if (process != null && process.isRunning()) {
332                 process.interrupt();
333             }
334         }
335     }
336 
337     /**
338      * Print stack trace of selected service processes to new window if the
339      * method <code>Thread.getStackTrace()</code> is supported form the currently running java
340      * version.
341      */
342     public void stackTraceSelected() {
343         if (!selectedProcesses.isEmpty()) {
344             StringBuffer result = new StringBuffer();
345             Iterator iterator = selectedProcesses.iterator();
346             while (iterator.hasNext()) {
347                 final ServiceProcess process = (ServiceProcessiterator.next();
348                 if (process != null && process.isRunning()
349                             && YodaUtility.existsMethod(Thread.class, "getStackTrace"new Class[] {})) {
350                     StackTraceElement[] trace = new StackTraceElement[] {};
351                     result.append("id ").append(process.getId());
352                     try {
353                         trace = (StackTraceElement[]) YodaUtility.executeMethod(
354                             process.getThread()"getStackTrace"new Class[] {}new Object[] {});
355                     catch (NoSuchMethodException e) {
356                         // ignore
357                     catch (InvocationTargetException e) {
358                         // ignore
359                     }
360                     for (int i = 0; i < trace.length; i++)  {
361                         result.append(trace[i]);
362                         result.append("\n");
363                     }
364                     result.append("\n\n");
365                 }
366             }
367             (new TextPaneWindow("Stacktrace",
368                 GuiHelper.readImageIcon("tango/16x16/devices/video-display.png"),
369                 result.toString())).setVisible(true);
370         }
371     }
372 
373 
374     /**
375      * Print details of selected service processes new window.
376      */
377     public void detailsSelected() {
378         if (!selectedProcesses.isEmpty()) {
379             StringBuffer result = new StringBuffer();
380             Iterator iterator = selectedProcesses.iterator();
381             while (iterator.hasNext()) {
382                 final ServiceProcess process = (ServiceProcessiterator.next();
383                 if (process != null) {
384                     result.append("id ").append(process.getId());
385                     String tip = "";
386                     if (process.isBlocked()) {
387                         tip = "Process is waiting";
388                     else if (process.isRunning()) {
389                         tip = "Process is running";
390                     else if (process.wasFailure()) {
391                         tip = "Process was stopped.";
392                     else if (process.wasSuccess()) {
393                         tip = "Process has finished";
394                     }
395                     result.append("\n\tStatus:     ").append(tip);
396                     result.append("\n\tAction:     ").append(process.getActionName());
397                     result.append("\n\tModule:     ").append(process.getQedeqName());
398                     result.append("\n\tURL:        ").append(process.getQedeqUrl());
399                     result.append("\n\tStart:      ").append(DateUtility.getIsoTime(process.getStart()));
400                     result.append("\n\tStop:       ").append((process.getStop() != 0
401                         ? DateUtility.getIsoTime(process.getStop()) ""));
402                     result.append("\n\tPercentage: ").append(process.getExecutionPercentage());
403                     result.append("\n\tDescription:").append(process.getExecutionActionDescription());
404                     result.append("\n\tBlocked:   ");
405                     final QedeqBoSet blocked = process.getBlockedModules();
406                     Iterator bIterator = blocked.iterator();
407                     while (bIterator.hasNext()) {
408                         final QedeqBo qedeq = (QedeqBobIterator.next();
409                         result.append(" ").append(qedeq.getName());
410                     }
411                     result.append("\n\tCalls:   ");
412                     PluginCall parent = process.getPluginCall();
413                     while (parent != null) {
414                         parent = parent.getParentPluginCall();
415                         if (parent != null) {
416                             result.append("\n\t\t ").append(parent.getId());
417                             tip = "";
418                             if (parent.isFinished()) {
419                                 tip = "Call is finished";
420                             else {
421                                 tip = "Call is running";
422                             }
423                             result.append("\n\t\t Status:     ").append(tip);
424                             result.append("\n\t\t Module:     ").append(parent.getQedeq().getName());
425                             result.append("\n\t\t Plugin:     ").append(parent.getPlugin().getPluginActionName());
426                             result.append("\n\t\t Start:      ").append(DateUtility.getIsoTime(parent.getStart()));
427                             result.append("\n\t\t Stop:       ").append((parent.getStop() != 0
428                                 ? DateUtility.getIsoTime(parent.getStop()) ""));
429                             result.append("\n\t\t Percentage: ").append(parent.getExecutionPercentage());
430                             result.append("\n\t\t Description:").append(parent.getExecutionActionDescription());
431                             result.append("\n\t\t Parameter:  ").append(parent.getParameterString());
432                         }
433                     }
434                     result.append("\n");
435                 }
436             }
437             (new TextPaneWindow("Service Process Details",
438                 GuiHelper.readImageIcon("tango/16x16/devices/video-display.png"),
439                 result.toString())).setVisible(true);
440         }
441     }
442 
443 
444 }