Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/geotools/gui/swing/LoggingPanel.java


1   /*
2    * -*- mode: java; c-basic-indent: 4; indent-tabs-mode: nil -*-
3    * :indentSize=4:noTabs=true:tabSize=4:indentOnTab=true:indentOnEnter=true:mode=java:
4    * ex: set tabstop=4 expandtab:
5    *
6    * MrPostman - webmail <-> email gateway
7    * Copyright (C) 2002-2003 MrPostman Development Group
8    * Projectpage: http://mrbook.org/mrpostman/
9    *
10   *
11   * This program is free software; you can redistribute it and/or modify
12   * it under the terms of the GNU General Public License as published by
13   * the Free Software Foundation; either version 2 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * In particular, this implies that users are responsible for
21   * using MrPostman after reading the terms and conditions given
22   * by their web-mail provider.
23   *
24   * You should have received a copy of the GNU General Public License
25   * Named LICENSE in the base directory of this distribution,
26   * if not, write to the Free Software
27   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28   */
29  
30  /*
31   * Geotools - OpenSource mapping toolkit
32   * (C) 2002, Centre for Computational Geography
33   * (C) 2002, Institut de Recherche pour le Développement
34   *
35   *    This library is free software; you can redistribute it and/or
36   *    modify it under the terms of the GNU Lesser General Public
37   *    License as published by the Free Software Foundation; either
38   *    version 2.1 of the License, or (at your option) any later version.
39   *
40   *    This library is distributed in the hope that it will be useful,
41   *    but WITHOUT ANY WARRANTY; without even the implied warranty of
42   *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43   *    Lesser General Public License for more details.
44   *
45   *    You should have received a copy of the GNU Lesser General Public
46   *    License along with this library; if not, write to the Free Software
47   *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
48   *
49   *
50   * Contacts:
51   *     UNITED KINGDOM: James Macgill
52   *             mailto:j.macgill@geog.leeds.ac.uk
53   *
54   *     FRANCE: Surveillance de l'Environnement Assistée par Satellite
55   *             Institut de Recherche pour le Développement / US-Espace
56   *             mailto:seasnet@teledetection.fr
57   *
58   *     CANADA: Observatoire du Saint-Laurent
59   *             Institut Maurice-Lamontagne
60   *             mailto:osl@osl.gc.ca
61   */
62  package org.geotools.gui.swing;
63  
64  import org.geotools.resources.SwingUtilities;
65  // Resources
66  import org.geotools.resources.XArray;
67  import org.geotools.resources.gui.ResourceKeys;
68  import org.geotools.resources.gui.Resources;
69  
70  import java.awt.BorderLayout;
71  // AWT
72  import java.awt.Color;
73  import java.awt.Component;
74  import java.awt.event.WindowAdapter;
75  import java.awt.event.WindowEvent;
76  
77  import java.util.ArrayList;
78  import java.util.Arrays;
79  // Collections
80  import java.util.List;
81  import java.util.logging.Handler;
82  // Logging
83  import java.util.logging.Level;
84  import java.util.logging.LogRecord;
85  import java.util.logging.Logger;
86  
87  import javax.swing.JPanel;
88  import javax.swing.JScrollPane;
89  // Swing dependencies
90  import javax.swing.JTable;
91  import javax.swing.event.ChangeEvent;
92  import javax.swing.event.ListSelectionEvent;
93  import javax.swing.event.TableColumnModelEvent;
94  import javax.swing.event.TableColumnModelListener;
95  import javax.swing.table.DefaultTableCellRenderer;
96  import javax.swing.table.TableColumn;
97  import javax.swing.table.TableColumnModel;
98  import javax.swing.table.TableModel;
99  
100 
101 /**
102  * A panel displaying logging messages. The windows displaying Geotools's logging messages
103  * can be constructed with the following code:
104  *
105  * <blockquote><pre>
106  * new LoggingPanel("org.geotools").{@link #show(Component) show}(null);
107  * </pre></blockquote>
108  *
109  * This panel is initially set to listen to messages of level {@link Level#CONFIG} or higher.
110  * This level can be changed with <code>{@link #getHandler}.setLevel(aLevel)</code>.
111  *
112  * @version $Id: LoggingPanel.java,v 1.5 2003/02/09 23:38:11 lbruand Exp $
113  * @author Martin Desruisseaux
114  */
115 public class LoggingPanel extends JPanel {
116     public static final String CVSID = "$Id: LoggingPanel.java,v 1.5 2003/02/09 23:38:11 lbruand Exp $";
117 
118     /**
119      * The model for this component.
120      */
121     private final LoggingTableModel model = new LoggingTableModel();
122 
123     /**
124      * The table for displaying logging messages.
125      */
126     private final JTable table = new JTable(model);
127 
128     /**
129      * The levels for colors enumerated in <code>levelColors</code>. This array
130      * <strong>must</strong> be in increasing order. Logging messages of level
131      * <code>levelValues[i]</code> or higher will be displayed with foreground
132      * color <code>levelColors[i*2]</code> and background color <code>levelColors[i*2+1]</code>.
133      *
134      * @see Level#intValue
135      * @see #getForeground(LogRecord)
136      * @see #getBackground(LogRecord)
137      */
138     private int[] levelValues = new int[0];
139 
140     /**
141      * Pairs of foreground and background colors to use for displaying logging messages.
142      * Logging messages of level <code>levelValues[i]</code> or higher will be displayed
143      * with foreground color <code>levelColors[i*2]</code> and background color
144      * <code>levelColors[i*2+1]</code>.
145      *
146      * @see #getForeground(LogRecord)
147      * @see #getBackground(LogRecord)
148      */
149     private final List levelColors = new ArrayList();
150 
151     /**
152      * The logger specified at construction time, or <code>null</code> if none.
153      */
154     private Logger logger;
155 
156     /**
157      * Constructs a new logging panel. This panel is not registered to any logger.
158      * Registration can be done with the following code:
159      *
160      * <blockquote><pre>
161      * logger.{@link Logger#addHandler addHandler}({@link #getHandler});
162      * </pre></blockquote>
163      */
164     public LoggingPanel() {
165         super(new BorderLayout());
166         table.setShowGrid(false);
167         table.setCellSelectionEnabled(false);
168         table.setGridColor(Color.LIGHT_GRAY);
169         table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
170         table.setDefaultRenderer(Object.class, new CellRenderer());
171 
172         if (true) {
173             int width = 300;
174             final TableColumnModel columns = table.getColumnModel();
175 
176             for (int i = model.getColumnCount(); --i >= 0;) {
177                 columns.getColumn(i).setPreferredWidth(width);
178                 width = 80;
179             }
180         }
181 
182         final JScrollPane scroll = new JScrollPane(table);
183         new AutoScroll(scroll.getVerticalScrollBar().getModel());
184         add(scroll, BorderLayout.CENTER);
185 
186         setLevelColor(Level.CONFIG, new Color(0, 128, 0), null);
187         setLevelColor(Level.WARNING, Color.RED, null);
188         setLevelColor(Level.SEVERE, Color.WHITE, Color.RED);
189     }
190 
191     /**
192      * Constructs a new logging panel and register it to the specified logger.
193      *
194      * @param logger The logger to listen to, or <code>null</code> for the root logger.
195      */
196     public LoggingPanel(Logger logger) {
197         this();
198 
199         if (logger == null) {
200             logger = Logger.getLogger("");
201         }
202         logger.addHandler(getHandler());
203         this.logger = logger;
204     }
205 
206     /**
207      * Construct a logging panel and register it to the specified logger.
208      *
209      * @param logger The logger name to listen to, or <code>null</code> for the root logger.
210      */
211     public LoggingPanel(final String logger) {
212         this(Logger.getLogger((logger != null) ? logger : ""));
213     }
214 
215     /**
216      * Returns the logging handler.
217      */
218     public Handler getHandler() {
219         return model;
220     }
221 
222     /**
223      * Returns the capacity. This is the maximum number of {@link LogRecord}s the handler
224      * can memorize. If more messages are logged, then the earliest messages will be discarted.
225      */
226     public int getCapacity() {
227         return model.getCapacity();
228     }
229 
230     /**
231      * Set the capacity. This is the maximum number of {@link LogRecord}s the handler can
232      * memorize. If more messages are logged, then the earliest messages will be discarted.
233      */
234     public void setCapacity(final int capacity) {
235         model.setCapacity(capacity);
236     }
237 
238     /**
239      * Returns the foreground color for the specified log record. This method is invoked at
240      * rendering time for every cell in the table's "message" column. The default implementation
241      * returns a color based on the record's level, using colors set with {@link #setLevelColor}.
242      *
243      * @param  record The record to get the foreground color.
244      * @return The foreground color for the specified record,
245      *         or <code>null</code> for the default color.
246      */
247     public Color getForeground(final LogRecord record) {
248         return getColor(record, 0);
249     }
250 
251     /**
252      * Returns the background color for the specified log record. This method is invoked at
253      * rendering time for every cell in the table's "message" column. The default implementation
254      * returns a color based on the record's level, using colors set with {@link #setLevelColor}.
255      *
256      * @param  record The record to get the background color.
257      * @return The background color for the specified record,
258      *         or <code>null</code> for the default color.
259      */
260     public Color getBackground(final LogRecord record) {
261         return getColor(record, 1);
262     }
263 
264     /**
265      * Returns the foreground or background color for the specified record.
266      *
267      * @param  record The record to get the color.
268      * @param  offset 0 for the foreground color, or 1 for the background color.
269      * @return The color for the specified record, or <code>null</code> for the default color.
270      */
271     private Color getColor(final LogRecord record, final int offset) {
272         int i = Arrays.binarySearch(levelValues, record.getLevel().intValue());
273 
274         if (i < 0) {
275             i = ~i - 1;   // "~" is the tild symbol, not minus.
276 
277             if (i < 0) {
278                 return null;
279             }
280         }
281         return (Color) levelColors.get((i * 2) + offset);
282     }
283 
284     /**
285      * Set the foreground and background colors for messages of the specified level.
286      * The specified colors will apply on any messages of level <code>level</code> or
287      * greater, up to the next level set with an other call to <code>setLevelColor(...)</code>.
288      *
289      * @param level       The minimal level to set color for.
290      * @param foreground  The foreground color, or <code>null</code> for the default color.
291      * @param background  The background color, or <code>null</code> for the default color.
292      */
293     public void setLevelColor(final Level level, final Color foreground, final Color background) {
294         final int value = level.intValue();
295         int i = Arrays.binarySearch(levelValues, value);
296 
297         if (i >= 0) {
298             i *= 2;
299             levelColors.set(i + 0, foreground);
300             levelColors.set(i + 1, background);
301         } else {
302             i = ~i;
303             levelValues = XArray.insert(levelValues, i, 1);
304             levelValues[i] = value;
305             i *= 2;
306             levelColors.add(i + 0, foreground);
307             levelColors.add(i + 1, background);
308         }
309         /*assert XArray.isSorted(levelValues);
310         assert levelValues.length*2 == levelColors.size();*/
311     }
312 
313     /**
314      * Layout this component. This method give all the remaining space, if any,
315      * to the last table's column. This column is usually the one with logging
316      * messages.
317      */
318     public void doLayout() {
319         final TableColumnModel model = table.getColumnModel();
320         final int messageColumn = model.getColumnCount() - 1;
321         Component parent = table.getParent();
322         int delta = parent.getWidth();
323 
324         if ((parent = parent.getParent()) instanceof JScrollPane) {
325             delta -= ((JScrollPane) parent).getVerticalScrollBar().getPreferredSize().width;
326         }
327 
328         for (int i = 0; i < messageColumn; i++) {
329             delta -= model.getColumn(i).getWidth();
330         }
331         final TableColumn column = model.getColumn(messageColumn);
332 
333         if (delta > Math.max(column.getWidth(), column.getPreferredWidth())) {
334             column.setPreferredWidth(delta);
335         }
336         super.doLayout();
337     }
338 
339     /**
340      * Convenience method showing this logging panel into a frame.
341      * Different kinds of frame can be constructed according <code>owner</code> class:
342      *
343      * <ul>
344      *   <li>If <code>owner</code> or one of its parent is a {@link JDesktopPane},
345      *       then <code>panel</code> is added into a {@link JInternalFrame}.</li>
346      *   <li>If <code>owner</code> or one of its parent is a {@link Frame} or a {@link Dialog},
347      *       then <code>panel</code> is added into a {@link JDialog}.</li>
348      *   <li>Otherwise, <code>panel</code> is added into a {@link JFrame}.</li>
349      * </ul>
350      *
351      * @param  owner The owner, or <code>null</code> to show
352      *         this logging panel in a top-level window.
353      * @return The frame. May be a {@link JInternalFrame},
354      *         a {@link JDialog} or a {@link JFrame}.
355      */
356     public Component show(final Component owner) {
357         final Component frame = SwingUtilities.toFrame(owner, this, Resources.format(ResourceKeys.EVENT_LOGGER),
358                 new WindowAdapter() {
359                     public void windowClosed(WindowEvent event) {
360                         dispose();
361                     }
362                 });
363         frame.setSize(750, 300);
364         frame.setVisible(true);
365         doLayout();
366         return frame;
367     }
368 
369     /**
370      * Free any resources used by this <code>LoggingPanel</code>. If a {@link Logger} was
371      * specified at construction time, then this method unregister the <code>LoggingPanel</code>'s
372      * handler from the specified logger. Next, {@link Handler#close} is invoked.
373      * <br><br>
374      * This method is invoked automatically when the user close the windows created
375      * with {@link #show(Component)}. If this <code>LoggingPanel</code> is displayed
376      * by some other ways (for example if it has been added into a {@link JPanel}),
377      * then this <code>dispose()</code> should be invoked explicitely when the container
378      * is being discarted.
379      */
380     public void dispose() {
381         final Handler handler = getHandler();
382 
383         while (logger != null) {
384             logger.removeHandler(handler);
385             logger = logger.getParent();
386         }
387         handler.close();
388     }
389 
390     /**
391      * Display cell contents. This class is used for changing
392      * the cell's color according the log record level.
393      */
394     private final class CellRenderer extends DefaultTableCellRenderer implements TableColumnModelListener {
395         /**
396          * Default color for the foreground.
397          */
398         private Color foreground;
399 
400         /**
401          * Default color for the background.
402          */
403         private Color background;
404 
405         /**
406          * The index of messages column.
407          */
408         private int messageColumn;
409 
410         /**
411          * The last row for which the side has been computed.
412          */
413         private int lastRow;
414 
415         /**
416          * Construct a new cell renderer.
417          */
418         public CellRenderer() {
419             foreground = super.getForeground();
420             background = super.getBackground();
421             table.getColumnModel().addColumnModelListener(this);
422         }
423 
424         /**
425          * Set the foreground color.
426          */
427         public void setForeground(final Color foreground) {
428             super.setForeground(this.foreground = foreground);
429         }
430 
431         /**
432          * Set the background colior
433          */
434         public void setBackground(final Color background) {
435             super.setBackground(this.background = background);
436         }
437 
438         /**
439          * Returns the component to use for painting the cell.
440          */
441         public Component getTableCellRendererComponent(final JTable table, final Object value,
442             final boolean isSelected, final boolean hasFocus, final int rowIndex, final int columnIndex) {
443             Color foreground = this.foreground;
444             Color background = this.background;
445             final boolean isMessage = (columnIndex == messageColumn);
446 
447             if (!isMessage) {
448                 foreground = Color.GRAY;
449             } else if (rowIndex >= 0) {
450                 final TableModel candidate = table.getModel();
451 
452                 if (candidate instanceof LoggingTableModel) {
453                     final LoggingTableModel model = (LoggingTableModel) candidate;
454                     final LogRecord record = model.getLogRecord(rowIndex);
455                     Color color;
456                     color = LoggingPanel.this.getForeground(record);
457 
458                     if (color != null) {
459                         foreground = color;
460                     }
461                     color = LoggingPanel.this.getBackground(record);
462 
463                     if (color != null) {
464                         background = color;
465                     }
466                 }
467             }
468             super.setBackground(background);
469             super.setForeground(foreground);
470             final Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
471                     rowIndex, columnIndex);
472 
473             /*
474              * If a new record is being painted and this new record is wider
475              * than previous ones, then make the message column width larger.
476              */
477             if (isMessage) {
478                 if (rowIndex > lastRow) {
479                     final int width = component.getPreferredSize().width + 15;
480                     final TableColumn column = table.getColumnModel().getColumn(columnIndex);
481 
482                     if (width > column.getPreferredWidth()) {
483                         column.setPreferredWidth(width);
484                     }
485 
486                     if (rowIndex == (lastRow + 1)) {
487                         lastRow = rowIndex;
488                     }
489                 }
490             }
491             return component;
492         }
493 
494         /**
495          * Invoked when the message column may have moved. This method update the
496          * {@link #messageColumn} field, so that the message column will continue
497          * to be paint with special colors.
498          */
499         private final void update() {
500             messageColumn = table.convertColumnIndexToView(model.getColumnCount() - 1);
501         }
502 
503         public void columnAdded(TableColumnModelEvent e) {
504             update();
505         }
506 
507         public void columnMarginChanged(ChangeEvent e) {
508             update();
509         }
510 
511         public void columnMoved(TableColumnModelEvent e) {
512             update();
513         }
514 
515         public void columnRemoved(TableColumnModelEvent e) {
516             update();
517         }
518 
519         public void columnSelectionChanged(ListSelectionEvent e) {
520             update();
521         }
522     }
523 }