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