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