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