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 &lt;sdeboy@apache.org&gt;
    77   * @author Paul Smith &lt;psmith@apache.org&gt;
    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 }