ModuleErrorAndWarningListPane.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2014,  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.Toolkit;
021 import java.awt.datatransfer.Clipboard;
022 import java.awt.datatransfer.StringSelection;
023 import java.awt.event.ActionEvent;
024 import java.awt.event.ActionListener;
025 import java.awt.event.MouseAdapter;
026 import java.awt.event.MouseEvent;
027 
028 import javax.swing.AbstractAction;
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.trace.Trace;
047 import org.qedeq.base.utility.DateUtility;
048 import org.qedeq.base.utility.EqualsUtility;
049 import org.qedeq.gui.se.control.SelectionListenerList;
050 import org.qedeq.gui.se.util.GuiHelper;
051 import org.qedeq.kernel.bo.KernelContext;
052 import org.qedeq.kernel.bo.common.QedeqBo;
053 import org.qedeq.kernel.bo.log.ModuleEventListener;
054 import org.qedeq.kernel.se.common.SourceFileException;
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 ModuleErrorAndWarningListPane extends JPanel implements ModuleEventListener,
068         ActionListener {
069 
070     /** This class. */
071     private static final Class CLASS = ModuleErrorAndWarningListPane.class;
072 
073     /** Currently selected error. */
074     private int selectedLine = -1;
075 
076     /** Table model. */
077     private ModuleErrorAndWarningListModel model = new ModuleErrorAndWarningListModel();
078 
079     /** This table holds the error descriptions. */
080     private JTable list = new JTable(model) {
081 
082         /**
083          * Just return the error message of the row.
084          *
085          @param  e   Mouse event.
086          @return Tool tip text.
087          */
088         public String getToolTipText(final MouseEvent e) {
089             String tip = null;
090             java.awt.Point p = e.getPoint();
091             int row = rowAtPoint(p);
092             SourceFileException sfe = null;
093             if (row >= 0) {
094                 try {
095                     sfe = model.getSourceFileException(row);
096                 catch (RuntimeException ex) {
097                     // ignore
098                 }
099             }
100             if (sfe != null) {
101                 int col = columnAtPoint(p);
102                 switch (col) {
103                 case 0: tip = (model.isWarning(row"Warning" "Error");
104                         break;
105                 case 1: tip = sfe.getMessage() "\n";
106                         break;
107                 case 2: tip = (sfe.getSourceArea() != null
108                             ? sfe.getSourceArea().getShortDescription() "");
109                         break;
110                 case 3: tip = sfe.getService().getServiceDescription();
111                         break;
112                 default: tip = sfe.getMessage() "\n";
113                 }
114                 tip = GuiHelper.getToolTipText(tip);
115                 return tip;
116 
117             }
118             return super.getToolTipText(e);
119         }
120 
121     };
122 
123     /** Context menu for right mouse click. */
124     private final ModuleErrorAndWarningListContextMenu contextMenu;
125 
126     /** Write with this font attributes. */
127     private final SimpleAttributeSet errorAttrs = new SimpleAttributeSet();
128 
129     /** For this module properties the warnings and errors are shown. */
130     private QedeqBo prop;
131 
132     /** Our scroll area. */
133     private JScrollPane scrollPane;
134 
135     /** This listener gets all selection messages from us. */
136     private SelectionListenerList listener;
137 
138     /**
139      * Creates new panel.
140      *
141      @param   listener    Send selecting events to this listener.
142      */
143     public ModuleErrorAndWarningListPane(final SelectionListenerList listener) {
144         super(false);
145         this.listener = listener;
146         this.contextMenu = new ModuleErrorAndWarningListContextMenu(this);
147         setModel(null);
148         setupView();
149     }
150 
151     /**
152      * Send selection events.
153      */
154     private void selectLine() {
155         Trace.param(CLASS, this, "selectLine""selectedLine", selectedLine);
156         if (model.isError(selectedLine)) {
157             listener.selectError(model.getErrorNumber(selectedLine), model.getSourceFileException(selectedLine));
158         else if (model.isWarning(selectedLine)) {
159             listener.selectWarning(model.getWarningNumber(selectedLine), model.getSourceFileException(selectedLine));
160         }
161     }
162 
163     /**
164      * Assembles the GUI components of the panel.
165      */
166     private final void setupView() {
167         FormLayout layout = new FormLayout(
168             "min:grow",
169             "0:grow");
170         final DefaultFormBuilder builder = new DefaultFormBuilder(layout, this);
171         builder.setBorder(BorderFactory.createEmptyBorder());
172         builder.setRowGroupingEnabled(true);
173 
174         final CellConstraints cc = new CellConstraints();
175         builder.appendRow(new RowSpec("0:grow"));
176 
177         list.setDefaultRenderer(Icon.class, new IconCellRenderer());
178 
179         final ListSelectionModel rowSM = list.getSelectionModel();
180         rowSM.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
181 
182         // if selection changes remember the error number (= selected row (starting with 0))
183         rowSM.addListSelectionListener(new ListSelectionListener() {
184             public void valueChanged(final ListSelectionEvent e) {
185                 if (e.getValueIsAdjusting()) {
186                     return;
187                 }
188                 selectedLine = list.getSelectionModel().getLeadSelectionIndex();
189                 Trace.param(CLASS, this, "setupView$valueChanged""selectedLine" , selectedLine);
190             }
191         });
192 
193         // doing a click shall open the edit window
194         list.addMouseListener(new MouseAdapter()  {
195             public void mousePressed(final MouseEvent evt) {
196                 if (SwingUtilities.isRightMouseButton(evt)) {
197                     contextMenu.show(evt.getComponent(),
198                         evt.getX(), evt.getY());
199                 else {
200                     super.mousePressed(evt);
201                 }
202             }
203 
204             public void mouseClicked(final MouseEvent e) {
205                 if (e.getClickCount() == 2) {
206                     Trace.trace(CLASS, this, "setupView$vmouseClicked""doubleClick");
207                 }
208                 selectLine();
209             }
210         });
211 
212         // pressing the enter key shall open the edit window
213         list.getActionMap().put("selectNextRowCell"new AbstractAction() {
214             public void actionPerformed(final ActionEvent event) {
215                 Trace.param(CLASS, this, "setupView$actionPerformed""event" , event);
216                 selectLine();
217             }
218         });
219 
220         scrollPane = new JScrollPane(list);
221         builder.add(scrollPane,
222             cc.xywh(builder.getColumn(), builder.getRow()12"fill, fill"));
223 
224         // open context menu on right click
225         scrollPane.addMouseListener(new MouseAdapter() {
226             public void mousePressed(final MouseEvent evt) {
227                 if (SwingUtilities.isRightMouseButton(evt)) {
228                     contextMenu.show(evt.getComponent(),
229                         evt.getX(), evt.getY());
230                 else {
231                     super.mousePressed(evt);
232                 }
233             }
234         });
235 
236         StyleConstants.setForeground(this.errorAttrs, Color.red);
237         // because we override getScrollableTracksViewportWidth:
238         scrollPane.getViewport().setBackground(Color.white);
239         StyleConstants.setBackground(this.errorAttrs, Color.white);  // TODO mime 20080124: testing
240 
241 
242         // the default table cell renderer uses a JLabel to render the heading and we want to
243         // left align the header columns
244         // TODO mime 20080415: left align with small spaces would be better
245         final JTableHeader header = list.getTableHeader();
246         ((JLabelheader.getDefaultRenderer()).setHorizontalAlignment(JLabel.LEFT);
247 
248         //set height of header correctly
249         header.setPreferredSize(new Dimension(list.getTableHeader().getWidth(),
250             (int) (1.1 this.getFontMetrics(getFont()).getHeight())));
251 
252         changeColumnHeaderWidth();
253     }
254 
255     /**
256      * Make 2. column smaller.
257      */
258     private void changeColumnHeaderWidth() {
259         TableColumnModel columnModel = list.getColumnModel();
260         columnModel.getColumn(0).setPreferredWidth(24);
261         columnModel.getColumn(0).setMinWidth(24);
262         columnModel.getColumn(1).setPreferredWidth(2000);
263         columnModel.getColumn(1).setMinWidth(100);
264         columnModel.getColumn(2).setPreferredWidth(50);
265         columnModel.getColumn(2).setMinWidth(50);
266         columnModel.getColumn(3).setPreferredWidth(100);
267         columnModel.getColumn(3).setMinWidth(100);
268     }
269 
270     /**
271      * Set new model. To make the new model visible {@link #updateView} must be called.
272      *
273      @param   prop    QEDEQ module.
274      */
275     public synchronized void setModel(final QedeqBo prop) {
276         Trace.trace(CLASS, this, "setModel", prop);
277         model.setQedeq(prop);
278         if (!EqualsUtility.equals(this.prop, prop)) {
279             this.prop = prop;
280             Runnable setModel = new Runnable() {
281                 public void run() {
282                     updateView();
283                 }
284             };
285             SwingUtilities.invokeLater(setModel);
286         }
287     }
288 
289     /**
290      * Update from model.
291      */
292     public synchronized void updateView() {
293         final String method = "updateView";
294         Trace.begin(CLASS, this, method);
295         ((AbstractTableModellist.getModel()).fireTableDataChanged();
296         ((AbstractTableModellist.getModel()).fireTableStructureChanged();
297         changeColumnHeaderWidth();
298         list.invalidate();
299         list.repaint();
300         this.repaint();
301     }
302 
303     public void addModule(final QedeqBo p) {
304         if (prop != null && prop.equals(p)) {
305             Runnable addModule = new Runnable() {
306                 public void run() {
307                     updateView();
308                 }
309             };
310             SwingUtilities.invokeLater(addModule);
311         }
312     }
313 
314     public void stateChanged(final QedeqBo p) {
315         if (prop != null && prop.equals(p)) {
316             Runnable stateChanged = new Runnable() {
317                 public void run() {
318                     updateView();
319                 }
320             };
321             SwingUtilities.invokeLater(stateChanged);
322         }
323     }
324 
325     public void removeModule(final QedeqBo p) {
326         if (prop != null && prop.equals(p)) {
327             prop = null;
328             Runnable removeModule = new Runnable() {
329                 public void run() {
330                     updateView();
331                 }
332             };
333             SwingUtilities.invokeLater(removeModule);
334         }
335     }
336 
337     public void actionPerformed(final ActionEvent actionevent) {
338         final String s = actionevent.getActionCommand();
339         if (s.equals("copy")) {
340             if (model.getQedeq() != null) {
341                 final StringBuffer sb = new StringBuffer(512);
342                 sb.append(KernelContext.getInstance().getDescriptiveKernelVersion() "\n");
343                 sb.append("Date\t" + DateUtility.getGmtTimestamp() "\n");
344                 sb.append("QEDEQ module\t" + prop.getUrl() "\n");
345                 sb.append("Kind\tType\tCode\tFrom\tTo\tDescription\n");
346                 for (int i = 0; i < model.getQedeq().getErrors().size(); i++) {
347                     SourceFileException e = model.getQedeq().getErrors().get(i);
348                     sb.append(e.getService().getServiceAction());
349                     sb.append("\t");
350                     sb.append("Error");
351                     sb.append("\t");
352                     sb.append(e.getErrorCode());
353                     sb.append("\t");
354                     sb.append(e.getSourceArea().getStartPosition());
355                     sb.append("\t");
356                     sb.append(e.getSourceArea().getEndPosition());
357                     sb.append("\t");
358                     sb.append(e.getMessage());
359                     sb.append("\n");
360                 }
361                 for (int i = 0; i < model.getQedeq().getWarnings().size(); i++) {
362                     SourceFileException e = model.getQedeq().getWarnings().get(i);
363                     sb.append(e.getService().getServiceAction());
364                     sb.append("\t");
365                     sb.append("Warning");
366                     sb.append("\t");
367                     sb.append(e.getErrorCode());
368                     sb.append("\t");
369                     sb.append(e.getSourceArea().getStartPosition());
370                     sb.append("\t");
371                     sb.append(e.getSourceArea().getEndPosition());
372                     sb.append("\t");
373                     sb.append(e.getMessage());
374                     sb.append("\n");
375                 }
376                 final Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
377                 StringSelection sel = new StringSelection(sb.toString());
378                 cb.setContents(sel, null);
379             }
380         }
381     }
382 
383 
384     /**
385      * We render our cells with this class.
386      *
387      @author  Michael Meyling
388      */
389     private static class IconCellRenderer extends DefaultTableCellRenderer {
390 
391         protected void setValue(final Object value) {
392             if (value instanceof Icon) {
393                 setIcon((Iconvalue);
394                 super.setValue(null);
395             else {
396                 setIcon(null);
397                 super.setValue(value);
398             }
399         }
400 
401     }
402 
403 }