View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.log4j.chainsaw;
19  
20  import java.awt.BorderLayout;
21  import java.awt.Color;
22  import java.awt.Component;
23  import java.awt.Dimension;
24  import java.awt.Toolkit;
25  import java.text.DateFormat;
26  import java.text.SimpleDateFormat;
27  import java.util.Date;
28  import java.util.Iterator;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.TimeZone;
32  
33  import javax.swing.BorderFactory;
34  import javax.swing.BoxLayout;
35  import javax.swing.Icon;
36  import javax.swing.JComponent;
37  import javax.swing.JLabel;
38  import javax.swing.JPanel;
39  import javax.swing.JTable;
40  import javax.swing.JTextPane;
41  import javax.swing.UIManager;
42  import javax.swing.border.Border;
43  import javax.swing.table.DefaultTableCellRenderer;
44  import javax.swing.table.TableColumn;
45  import javax.swing.text.AbstractDocument;
46  import javax.swing.text.BadLocationException;
47  import javax.swing.text.BoxView;
48  import javax.swing.text.ComponentView;
49  import javax.swing.text.Element;
50  import javax.swing.text.IconView;
51  import javax.swing.text.LabelView;
52  import javax.swing.text.MutableAttributeSet;
53  import javax.swing.text.ParagraphView;
54  import javax.swing.text.SimpleAttributeSet;
55  import javax.swing.text.Style;
56  import javax.swing.text.StyleConstants;
57  import javax.swing.text.StyledDocument;
58  import javax.swing.text.StyledEditorKit;
59  import javax.swing.text.TabSet;
60  import javax.swing.text.TabStop;
61  import javax.swing.text.View;
62  import javax.swing.text.ViewFactory;
63  
64  import org.apache.log4j.chainsaw.color.RuleColorizer;
65  import org.apache.log4j.chainsaw.icons.LevelIconFactory;
66  import org.apache.log4j.helpers.Constants;
67  import org.apache.log4j.rule.Rule;
68  import org.apache.log4j.spi.LoggingEventFieldResolver;
69  
70  
71  /**
72   * A specific TableCellRenderer that colourizes a particular cell based on
73   * some ColourFilters that have been stored according to the value for the row
74   *
75   * @author Claude Duguay
76   * @author Scott Deboy <sdeboy@apache.org>
77   * @author Paul Smith <psmith@apache.org>
78   *
79   */
80  public class TableColorizingRenderer extends DefaultTableCellRenderer {
81    private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(Constants.SIMPLE_TIME_PATTERN);
82    private static final Map iconMap = LevelIconFactory.getInstance().getLevelToIconMap();
83    private RuleColorizer colorizer;
84    private boolean levelUseIcons = false;
85    private boolean wrap = false;
86    private boolean highlightSearchMatchText;
87    private DateFormat dateFormatInUse = DATE_FORMATTER;
88    private int loggerPrecision = 0;
89    private boolean toolTipsVisible;
90    private String dateFormatTZ;
91    private boolean useRelativeTimesToFixedTime = false;
92    private long relativeTimestampBase;
93  
94    private static int borderWidth = ChainsawConstants.TABLE_BORDER_WIDTH;
95  
96    private final Color borderColor;
97  
98    private final JTextPane levelTextPane = new JTextPane();
99    private JTextPane singleLineTextPane = new JTextPane();
100 
101   private final JPanel multiLinePanel = new JPanel(new BorderLayout());
102   private final JPanel generalPanel = new JPanel(new BorderLayout());
103   private final JPanel levelPanel = new JPanel(new BorderLayout());
104   private ApplicationPreferenceModel applicationPreferenceModel;
105   private JTextPane multiLineTextPane;
106   private MutableAttributeSet boldAttributeSet;
107   private TabSet tabs;
108   private int maxHeight;
109   private boolean useRelativeTimesToPrevious;
110   private EventContainer eventContainer;
111   private LogPanelPreferenceModel logPanelPreferenceModel;
112   private SimpleAttributeSet insetAttributeSet;
113   private boolean colorizeSearch;
114 
115   /**
116    * Creates a new TableColorizingRenderer object.
117    */
118   public TableColorizingRenderer(RuleColorizer colorizer, ApplicationPreferenceModel applicationPreferenceModel,
119                                  EventContainer eventContainer, LogPanelPreferenceModel logPanelPreferenceModel,
120                                  boolean colorizeSearch) {
121     this.applicationPreferenceModel = applicationPreferenceModel;
122     this.logPanelPreferenceModel = logPanelPreferenceModel;
123     this.eventContainer = eventContainer;
124     this.colorizeSearch = colorizeSearch;
125     multiLinePanel.setLayout(new BoxLayout(multiLinePanel, BoxLayout.Y_AXIS));
126     generalPanel.setLayout(new BoxLayout(generalPanel, BoxLayout.Y_AXIS));
127     levelPanel.setLayout(new BoxLayout(levelPanel, BoxLayout.Y_AXIS));
128     maxHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
129 
130     if (UIManager.get("Table.selectionBackground") != null) {
131         borderColor = (Color)UIManager.get("Table.selectionBackground");
132     } else {
133         borderColor = Color.BLUE;
134     }
135     //define the 'bold' attributeset
136     boldAttributeSet = new SimpleAttributeSet();
137     StyleConstants.setBold(boldAttributeSet, true);
138 
139     insetAttributeSet = new SimpleAttributeSet();
140     StyleConstants.setLeftIndent(insetAttributeSet, 6);
141     //throwable col may have a tab..if so, render the tab as col zero
142     int pos = 0;
143     int align = TabStop.ALIGN_LEFT;
144     int leader = TabStop.LEAD_NONE;
145     TabStop tabStop = new TabStop(pos, align, leader);
146     tabs = new TabSet(new TabStop[]{tabStop});
147 
148     levelTextPane.setOpaque(true);
149     levelTextPane.setText("");
150 
151     levelPanel.add(levelTextPane);
152 
153     this.colorizer = colorizer;
154     multiLineTextPane = new JTextPane();
155     multiLineTextPane.setEditorKit(new StyledEditorKit());
156 
157     singleLineTextPane.setEditorKit(new OneLineEditorKit());
158     levelTextPane.setEditorKit(new OneLineEditorKit());
159 
160     multiLineTextPane.setEditable(false);
161     multiLineTextPane.setFont(levelTextPane.getFont());
162 
163     multiLineTextPane.setParagraphAttributes(insetAttributeSet, false);
164     singleLineTextPane.setParagraphAttributes(insetAttributeSet, false);
165     levelTextPane.setParagraphAttributes(insetAttributeSet, false);
166   }
167 
168   public void setToolTipsVisible(boolean toolTipsVisible) {
169       this.toolTipsVisible = toolTipsVisible;
170   }
171 
172   public Component getTableCellRendererComponent(
173     final JTable table, Object value, boolean isSelected, boolean hasFocus,
174     int row, int col) {
175     EventContainer container = (EventContainer) table.getModel();
176     LoggingEventWrapper loggingEventWrapper = container.getRow(row);
177     value = formatField(value, loggingEventWrapper);
178     TableColumn tableColumn = table.getColumnModel().getColumn(col);
179     int width = tableColumn.getWidth();
180     JLabel label = (JLabel)super.getTableCellRendererComponent(table, value,
181         isSelected, hasFocus, row, col);
182     //chainsawcolumns uses one-based indexing
183     int colIndex = tableColumn.getModelIndex() + 1;
184 
185     //no event, use default renderer
186     if (loggingEventWrapper == null) {
187         return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
188     }
189     long delta = 0;
190     if (row > 0) {
191         LoggingEventWrapper previous = eventContainer.getRow(row - 1);
192         delta = Math.min(ChainsawConstants.MILLIS_DELTA_RENDERING_HEIGHT_MAX, Math.max(0, (long) ((loggingEventWrapper.getLoggingEvent().getTimeStamp() - previous.getLoggingEvent().getTimeStamp()) * ChainsawConstants.MILLIS_DELTA_RENDERING_FACTOR)));
193     }
194 
195     Map matches = loggingEventWrapper.getSearchMatches();
196 
197     JComponent component;
198     switch (colIndex) {
199     case ChainsawColumns.INDEX_THROWABLE_COL_NAME:
200       if (value instanceof String[] && ((String[])value).length > 0){
201           Style tabStyle = singleLineTextPane.getLogicalStyle();
202           StyleConstants.setTabSet(tabStyle, tabs);
203           //set the 1st tab at position 3
204           singleLineTextPane.setLogicalStyle(tabStyle);
205           //exception string is split into an array..just highlight the first line completely if anything in the exception matches if we have a match for the exception field
206           Set exceptionMatches = (Set)matches.get(LoggingEventFieldResolver.EXCEPTION_FIELD);
207           if (exceptionMatches != null && exceptionMatches.size() > 0) {
208               singleLineTextPane.setText(((String[])value)[0]);
209               boldAll((StyledDocument) singleLineTextPane.getDocument());
210           } else {
211               singleLineTextPane.setText(((String[])value)[0]);
212           }
213       } else {
214         singleLineTextPane.setText("");
215       }
216       layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
217       component = generalPanel;
218       break;
219     case ChainsawColumns.INDEX_LOGGER_COL_NAME:
220       String logger = value.toString();
221       int startPos = -1;
222 
223       for (int i = 0; i < loggerPrecision; i++) {
224         startPos = logger.indexOf(".", startPos + 1);
225         if (startPos < 0) {
226           break;
227         }
228       }
229         singleLineTextPane.setText(logger.substring(startPos + 1));
230         setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.LOGGER_FIELD), (StyledDocument) singleLineTextPane.getDocument());
231         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
232         component = generalPanel;
233       break;
234     case ChainsawColumns.INDEX_ID_COL_NAME:
235         singleLineTextPane.setText(value.toString());
236         setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.PROP_FIELD + "LOG4JID"), (StyledDocument) singleLineTextPane.getDocument());
237         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
238         component = generalPanel;
239         break;
240     case ChainsawColumns.INDEX_CLASS_COL_NAME:
241         singleLineTextPane.setText(value.toString());
242         setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.CLASS_FIELD), (StyledDocument) singleLineTextPane.getDocument());
243         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
244         component = generalPanel;
245         break;
246     case ChainsawColumns.INDEX_FILE_COL_NAME:
247         singleLineTextPane.setText(value.toString());
248         setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.FILE_FIELD), (StyledDocument) singleLineTextPane.getDocument());
249         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
250         component = generalPanel;
251         break;
252     case ChainsawColumns.INDEX_LINE_COL_NAME:
253         singleLineTextPane.setText(value.toString());
254         setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.LINE_FIELD), (StyledDocument) singleLineTextPane.getDocument());
255         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
256         component = generalPanel;
257         break;
258     case ChainsawColumns.INDEX_NDC_COL_NAME:
259         singleLineTextPane.setText(value.toString());
260         setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.NDC_FIELD), (StyledDocument) singleLineTextPane.getDocument());
261         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
262         component = generalPanel;
263         break;
264     case ChainsawColumns.INDEX_THREAD_COL_NAME:
265         singleLineTextPane.setText(value.toString());
266         setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.THREAD_FIELD), (StyledDocument) singleLineTextPane.getDocument());
267         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
268         component = generalPanel;
269         break;
270     case ChainsawColumns.INDEX_TIMESTAMP_COL_NAME:
271         //timestamp matches contain the millis..not the display text..just highlight if we have a match for the timestamp field
272         Set timestampMatches = (Set)matches.get(LoggingEventFieldResolver.TIMESTAMP_FIELD);
273         if (timestampMatches != null && timestampMatches.size() > 0) {
274             singleLineTextPane.setText(value.toString());
275             boldAll((StyledDocument) singleLineTextPane.getDocument());
276         } else {
277             singleLineTextPane.setText(value.toString());
278         }
279         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
280         component = generalPanel;
281         break;
282     case ChainsawColumns.INDEX_METHOD_COL_NAME:
283         singleLineTextPane.setText(value.toString());
284         setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.METHOD_FIELD), (StyledDocument) singleLineTextPane.getDocument());
285         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
286         component = generalPanel;
287         break;
288     case ChainsawColumns.INDEX_LOG4J_MARKER_COL_NAME:
289     case ChainsawColumns.INDEX_MESSAGE_COL_NAME:
290         String thisString = value.toString().trim();
291         JTextPane textPane = wrap ? multiLineTextPane : singleLineTextPane;
292         JComponent textPaneContainer = wrap ? multiLinePanel : generalPanel;
293         textPane.setText(thisString);
294 
295         if (colIndex == ChainsawColumns.INDEX_LOG4J_MARKER_COL_NAME) {
296             //property keys are set as all uppercase
297             setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.PROP_FIELD + ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE.toUpperCase()), (StyledDocument) textPane.getDocument());
298         } else {
299             setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.MSG_FIELD), (StyledDocument) textPane.getDocument());
300         }
301         textPaneContainer.removeAll();
302         if (delta > 0 && logPanelPreferenceModel.isShowMillisDeltaAsGap()) {
303             JPanel newPanel = new JPanel();
304             newPanel.setOpaque(true);
305             newPanel.setBackground(applicationPreferenceModel.getDeltaColor());
306             newPanel.setPreferredSize(new Dimension(width, (int) delta));
307             textPaneContainer.add(newPanel, BorderLayout.NORTH);
308         }
309         textPaneContainer.add(textPane, BorderLayout.SOUTH);
310 
311         if (delta == 0 || !logPanelPreferenceModel.isShowMillisDeltaAsGap()) {
312           if (col == 0) {
313             textPane.setBorder(getLeftBorder(isSelected, delta));
314           } else if (col == table.getColumnCount() - 1) {
315             textPane.setBorder(getRightBorder(isSelected, delta));
316           } else {
317             textPane.setBorder(getMiddleBorder(isSelected, delta));
318           }
319         } else {
320             if (col == 0) {
321               textPane.setBorder(getLeftBorder(isSelected, 0));
322             } else if (col == table.getColumnCount() - 1) {
323               textPane.setBorder(getRightBorder(isSelected, 0));
324             } else {
325               textPane.setBorder(getMiddleBorder(isSelected, 0));
326             }
327         }
328         int currentMarkerHeight = loggingEventWrapper.getMarkerHeight();
329         int currentMsgHeight = loggingEventWrapper.getMsgHeight();
330         int newRowHeight = ChainsawConstants.DEFAULT_ROW_HEIGHT;
331         boolean setHeight = false;
332 
333         if (wrap) {
334             /*
335             calculating the height -would- be the correct thing to do, but setting the size to screen size works as well and
336             doesn't incur massive overhead, like calculateHeight does
337             Map paramMap = new HashMap();
338             paramMap.put(TextAttribute.FONT, multiLineTextPane.getFont());
339 
340             int calculatedHeight = calculateHeight(thisString, width, paramMap);
341              */
342             //instead, set size to max height
343             textPane.setSize(new Dimension(width, maxHeight));
344             int multiLinePanelPrefHeight = textPaneContainer.getPreferredSize().height;
345             newRowHeight = Math.max(ChainsawConstants.DEFAULT_ROW_HEIGHT, multiLinePanelPrefHeight);
346 
347         }
348         if (!wrap && logPanelPreferenceModel.isShowMillisDeltaAsGap()) {
349             textPane.setSize(new Dimension(Integer.MAX_VALUE, ChainsawConstants.DEFAULT_ROW_HEIGHT));
350             newRowHeight = (int) (ChainsawConstants.DEFAULT_ROW_HEIGHT + delta);
351         }
352 
353         if (colIndex == ChainsawColumns.INDEX_LOG4J_MARKER_COL_NAME) {
354             loggingEventWrapper.setMarkerHeight(newRowHeight);
355             if (newRowHeight != currentMarkerHeight && newRowHeight >= loggingEventWrapper.getMsgHeight()) {
356                 setHeight = true;
357             }
358         }
359 
360         if (colIndex == ChainsawColumns.INDEX_MESSAGE_COL_NAME) {
361             loggingEventWrapper.setMsgHeight(newRowHeight);
362             if (newRowHeight != currentMsgHeight && newRowHeight >= loggingEventWrapper.getMarkerHeight()) {
363                 setHeight = true;
364             }
365         }
366         if (setHeight) {
367             table.setRowHeight(row, newRowHeight);
368         }
369 
370         component = textPaneContainer;
371         break;
372     case ChainsawColumns.INDEX_LEVEL_COL_NAME:
373       if (levelUseIcons) {
374         levelTextPane.setText("");
375         levelTextPane.insertIcon((Icon) iconMap.get(value.toString()));
376         if (!toolTipsVisible) {
377           levelTextPane.setToolTipText(value.toString());
378         }
379       } else {
380         levelTextPane.setText(value.toString());
381         setHighlightAttributesInternal(matches.get(LoggingEventFieldResolver.LEVEL_FIELD), (StyledDocument) levelTextPane.getDocument());
382         if (!toolTipsVisible) {
383             levelTextPane.setToolTipText(null);
384         }
385       }
386       if (toolTipsVisible) {
387           levelTextPane.setToolTipText(label.getToolTipText());
388       }
389       levelTextPane.setForeground(label.getForeground());
390       levelTextPane.setBackground(label.getBackground());
391       layoutRenderingPanel(levelPanel, levelTextPane, delta, isSelected, width, col, table);
392       component = levelPanel;
393       break;
394 
395     //remaining entries are properties
396     default:
397         Set propertySet = loggingEventWrapper.getPropertyKeySet();
398         String headerName = tableColumn.getHeaderValue().toString().toLowerCase();
399         String thisProp = null;
400         //find the property in the property set...case-sensitive
401         for (Iterator iter = propertySet.iterator();iter.hasNext();) {
402             String entry = iter.next().toString();
403             if (entry.equalsIgnoreCase(headerName)) {
404                 thisProp = entry;
405                 break;
406             }
407         }
408         if (thisProp != null) {
409             String propKey = LoggingEventFieldResolver.PROP_FIELD + thisProp.toUpperCase();
410             Set propKeyMatches = (Set)matches.get(propKey);
411             singleLineTextPane.setText(loggingEventWrapper.getLoggingEvent().getProperty(thisProp));
412             setHighlightAttributesInternal(propKeyMatches, (StyledDocument) singleLineTextPane.getDocument());
413         } else {
414             singleLineTextPane.setText("");
415         }
416         layoutRenderingPanel(generalPanel, singleLineTextPane, delta, isSelected, width, col, table);
417         component = generalPanel;
418         break;
419     }
420 
421     Color background;
422     Color foreground;
423     Rule loggerRule = colorizer.getLoggerRule();
424     //use logger colors in table instead of event colors if event passes logger rule
425     if (loggerRule != null && loggerRule.evaluate(loggingEventWrapper.getLoggingEvent(), null)) {
426         background = applicationPreferenceModel.getSearchBackgroundColor();
427         foreground = applicationPreferenceModel.getSearchForegroundColor();
428     } else {
429         if (colorizeSearch && !applicationPreferenceModel.isBypassSearchColors()) {
430           background = loggingEventWrapper.isSearchMatch()?applicationPreferenceModel.getSearchBackgroundColor():loggingEventWrapper.getBackground();
431           foreground = loggingEventWrapper.isSearchMatch()?applicationPreferenceModel.getSearchForegroundColor():loggingEventWrapper.getForeground();
432         } else {
433           background = loggingEventWrapper.getBackground();
434           foreground = loggingEventWrapper.getForeground();
435         }
436     }
437 
438     /**
439      * Colourize background based on row striping if the event still has default foreground and background color
440      */
441     if (background.equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND) && foreground.equals(ChainsawConstants.COLOR_DEFAULT_FOREGROUND) && (row % 2) != 0) {
442       background = applicationPreferenceModel.getAlternatingColorBackgroundColor();
443       foreground = applicationPreferenceModel.getAlternatingColorForegroundColor();
444     }
445 
446     component.setBackground(background);
447     component.setForeground(foreground);
448 
449     //update the background & foreground of the jtextpane using styles
450     if (multiLineTextPane != null)
451     {
452         updateColors(multiLineTextPane, background, foreground);
453     }
454     updateColors(levelTextPane, background, foreground);
455     updateColors(singleLineTextPane, background, foreground);
456 
457     return component;
458   }
459 
460     private void layoutRenderingPanel(JComponent container, JComponent bottomComponent, long delta, boolean isSelected,
461                                       int width, int col, JTable table) {
462         container.removeAll();
463         if (delta == 0 || !logPanelPreferenceModel.isShowMillisDeltaAsGap()) {
464           if (col == 0) {
465             bottomComponent.setBorder(getLeftBorder(isSelected, delta));
466           } else if (col == table.getColumnCount() - 1) {
467             bottomComponent.setBorder(getRightBorder(isSelected, delta));
468           } else {
469             bottomComponent.setBorder(getMiddleBorder(isSelected, delta));
470           }
471         } else {
472             JPanel newPanel = new JPanel();
473             newPanel.setOpaque(true);
474             newPanel.setBackground(applicationPreferenceModel.getDeltaColor());
475             newPanel.setPreferredSize(new Dimension(width, (int) delta));
476             container.add(newPanel, BorderLayout.NORTH);
477             if (col == 0) {
478               bottomComponent.setBorder(getLeftBorder(isSelected, 0));
479             } else if (col == table.getColumnCount() - 1) {
480               bottomComponent.setBorder(getRightBorder(isSelected, 0));
481             } else {
482               bottomComponent.setBorder(getMiddleBorder(isSelected, 0));
483             }
484         }
485 
486         container.add(bottomComponent, BorderLayout.SOUTH);
487     }
488 
489     private Border getLeftBorder(boolean isSelected, long delta) {
490         Border LEFT_BORDER = BorderFactory.createMatteBorder(borderWidth, borderWidth, borderWidth, 0, borderColor);
491         Border LEFT_EMPTY_BORDER = BorderFactory.createEmptyBorder(borderWidth, borderWidth, borderWidth, 0);
492 
493         Border innerBorder =isSelected?LEFT_BORDER : LEFT_EMPTY_BORDER;
494         if (delta == 0 || !wrap || !logPanelPreferenceModel.isShowMillisDeltaAsGap()) {
495             return innerBorder;
496         } else {
497             Border outerBorder = BorderFactory.createMatteBorder((int) Math.max(borderWidth, delta), 0, 0, 0, applicationPreferenceModel.getDeltaColor());
498             return BorderFactory.createCompoundBorder(outerBorder, innerBorder);
499         }
500     }
501 
502     private Border getRightBorder(boolean isSelected, long delta) {
503         Border RIGHT_BORDER = BorderFactory.createMatteBorder(borderWidth, 0, borderWidth, borderWidth, borderColor);
504         Border RIGHT_EMPTY_BORDER = BorderFactory.createEmptyBorder(borderWidth, 0, borderWidth, borderWidth);
505         Border innerBorder =isSelected?RIGHT_BORDER : RIGHT_EMPTY_BORDER;
506         if (delta == 0 || !wrap || !logPanelPreferenceModel.isShowMillisDeltaAsGap()) {
507             return innerBorder;
508         } else {
509             Border outerBorder = BorderFactory.createMatteBorder((int) Math.max(borderWidth, delta), 0, 0, 0, applicationPreferenceModel.getDeltaColor());
510             return BorderFactory.createCompoundBorder(outerBorder, innerBorder);
511         }
512     }
513 
514     private Border getMiddleBorder(boolean isSelected, long delta) {
515         Border MIDDLE_BORDER = BorderFactory.createMatteBorder(borderWidth, 0, borderWidth, 0, borderColor);
516         Border MIDDLE_EMPTY_BORDER = BorderFactory.createEmptyBorder(borderWidth, 0, borderWidth, 0);
517         Border innerBorder =isSelected ?MIDDLE_BORDER : MIDDLE_EMPTY_BORDER;
518         if (delta == 0 || !wrap || !logPanelPreferenceModel.isShowMillisDeltaAsGap()) {
519             return innerBorder;
520         } else {
521             Border outerBorder = BorderFactory.createMatteBorder((int)Math.max(borderWidth, delta), 0, 0, 0, applicationPreferenceModel.getDeltaColor());
522             return BorderFactory.createCompoundBorder(outerBorder, innerBorder);
523         }
524     }
525 
526     private void updateColors(JTextPane textPane, Color background, Color foreground)
527     {
528         StyledDocument styledDocument = textPane.getStyledDocument();
529         MutableAttributeSet attributes = textPane.getInputAttributes();
530         StyleConstants.setForeground(attributes, foreground);
531         styledDocument.setCharacterAttributes(0, styledDocument.getLength() + 1, attributes, false);
532         textPane.setBackground(background);
533     }
534 
535   /**
536    * Changes the Date Formatting object to be used for rendering dates.
537    * @param formatter
538    */
539   void setDateFormatter(DateFormat formatter) {
540     this.dateFormatInUse = formatter;
541     if (dateFormatInUse != null && dateFormatTZ != null && !("".equals(dateFormatTZ))) {
542       dateFormatInUse.setTimeZone(TimeZone.getTimeZone(dateFormatTZ));
543     } else {
544       dateFormatInUse.setTimeZone(TimeZone.getDefault());
545     }
546   }
547 
548   /**
549    * Changes the Logger precision.
550    * @param loggerPrecisionText
551    */
552   void setLoggerPrecision(String loggerPrecisionText) {
553     try {
554       loggerPrecision = Integer.parseInt(loggerPrecisionText);
555     } catch (NumberFormatException nfe) {
556         loggerPrecision = 0;
557     }
558   }
559 
560   /**
561    *Format date field
562    *
563    * @param field object
564    *
565    * @return formatted object
566    */
567   private Object formatField(Object field, LoggingEventWrapper loggingEventWrapper) {
568     if (!(field instanceof Date)) {
569       return (field == null ? "" : field);
570     }
571 
572     //handle date field
573     if (useRelativeTimesToFixedTime) {
574         return "" + (((Date)field).getTime() - relativeTimestampBase);
575     }
576     if (useRelativeTimesToPrevious) {
577         return loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE);
578     }
579 
580     return dateFormatInUse.format((Date) field);
581   }
582 
583     /**
584     * Sets the property which determines whether to wrap the message
585     * @param wrapMsg
586     */
587    public void setWrapMessage(boolean wrapMsg) {
588      this.wrap = wrapMsg;
589    }
590 
591    /**
592    * Sets the property which determines whether to use Icons or text
593    * for the Level column
594    * @param levelUseIcons
595    */
596   public void setLevelUseIcons(boolean levelUseIcons) {
597     this.levelUseIcons = levelUseIcons;
598   }
599 
600   public void setTimeZone(String dateFormatTZ) {
601     this.dateFormatTZ = dateFormatTZ;
602 
603     if (dateFormatInUse != null && dateFormatTZ != null && !("".equals(dateFormatTZ))) {
604       dateFormatInUse.setTimeZone(TimeZone.getTimeZone(dateFormatTZ));
605     } else {
606       dateFormatInUse.setTimeZone(TimeZone.getDefault());
607     }
608   }
609 
610   public void setUseRelativeTimes(long timeStamp) {
611     useRelativeTimesToFixedTime = true;
612     useRelativeTimesToPrevious = false;
613     relativeTimestampBase = timeStamp;
614   }
615 
616   public void setUseRelativeTimesToPreviousRow() {
617      useRelativeTimesToFixedTime = false;
618      useRelativeTimesToPrevious = true;
619   }
620 
621   public void setUseNormalTimes() {
622     useRelativeTimesToFixedTime = false;
623     useRelativeTimesToPrevious = false;
624   }
625 
626   /*
627    private int calculateHeight(String string, int width, Map paramMap) {
628      if (string.trim().length() == 0) {
629          return ChainsawConstants.DEFAULT_ROW_HEIGHT;
630      }
631      AttributedCharacterIterator paragraph = new AttributedString(string, paramMap).getIterator();
632      LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, new FontRenderContext(null, true, true));
633      float height = 0;
634      lineMeasurer.setPosition(paragraph.getBeginIndex());
635      TextLayout layout;
636      while (lineMeasurer.getPosition() < paragraph.getEndIndex()) {
637        layout = lineMeasurer.nextLayout(width);
638          float layoutHeight = layout.getAscent() + layout.getDescent() + layout.getLeading();
639          height += layoutHeight;
640      }
641      return Math.max(ChainsawConstants.DEFAULT_ROW_HEIGHT, (int) height);
642     }
643     */
644 
645     private void setHighlightAttributesInternal(Object matchSet, StyledDocument styledDocument) {
646         if (!highlightSearchMatchText) {
647             return;
648         }
649         setHighlightAttributes(matchSet, styledDocument);
650     }
651 
652     public void setHighlightAttributes(Object matchSet, StyledDocument styledDocument) {
653         if (matchSet instanceof Set) {
654             Set thisSet = (Set)matchSet;
655             for (Iterator iter = thisSet.iterator();iter.hasNext();) {
656                 String thisEntry = iter.next().toString();
657                 bold(thisEntry, styledDocument);
658             }
659         }
660     }
661 
662     private void boldAll(StyledDocument styledDocument) {
663         if (!highlightSearchMatchText) {
664             return;
665         }
666         styledDocument.setCharacterAttributes(0, styledDocument.getLength(), boldAttributeSet, false);
667     }
668     
669     private void bold(String textToBold, StyledDocument styledDocument) {
670         try {
671             String lowerInput = styledDocument.getText(0, styledDocument.getLength()).toLowerCase();
672             String lowerTextToBold = textToBold.toLowerCase();
673             int textToBoldLength = textToBold.length();
674             int firstIndex = 0;
675             int currentIndex;
676             while ((currentIndex = lowerInput.indexOf(lowerTextToBold, firstIndex)) > -1) {
677                 styledDocument.setCharacterAttributes(currentIndex, textToBoldLength, boldAttributeSet, false);
678                 firstIndex = currentIndex + textToBoldLength;
679             }
680         }
681         catch (BadLocationException e) {
682             //ignore
683         }
684     }
685 
686     public void setHighlightSearchMatchText(boolean highlightSearchMatchText)
687     {
688         this.highlightSearchMatchText = highlightSearchMatchText;
689     }
690 
691     private class OneLineEditorKit extends StyledEditorKit {
692         private ViewFactory viewFactoryImpl = new ViewFactoryImpl();
693 
694         public ViewFactory getViewFactory() {
695             return viewFactoryImpl;
696         }
697     }
698 
699     private class ViewFactoryImpl implements ViewFactory {
700         public View create(Element elem)
701         {
702             String elementName = elem.getName();
703             if (elementName != null)
704             {
705                 if (elementName.equals(AbstractDocument.ParagraphElementName)) {
706                     return new OneLineParagraphView(elem);
707                 } else  if (elementName.equals(AbstractDocument.ContentElementName)) {
708                     return new LabelView(elem);
709                 } else if (elementName.equals(AbstractDocument.SectionElementName)) {
710                     return new BoxView(elem, View.Y_AXIS);
711                 } else if (elementName.equals(StyleConstants.ComponentElementName)) {
712                     return new ComponentView(elem);
713                 } else if (elementName.equals(StyleConstants.IconElementName)) {
714                     return new IconView(elem);
715                 }
716             }
717             return new LabelView(elem);
718         }
719     }
720 
721     private class OneLineParagraphView extends ParagraphView {
722         public OneLineParagraphView(Element elem) {
723             super(elem);
724         }
725 
726         //this is the main fix - set the flow span to be max val
727         public int getFlowSpan(int index) {
728             return Integer.MAX_VALUE;
729         }
730     }
731 }