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.Container;
    24  import java.awt.Dimension;
    25  import java.awt.EventQueue;
    26  import java.awt.Font;
    27  import java.awt.FontMetrics;
    28  import java.awt.Graphics;
    29  import java.awt.Point;
    30  import java.awt.Toolkit;
    31  import java.awt.Window;
    32  import java.awt.datatransfer.Clipboard;
    33  import java.awt.datatransfer.StringSelection;
    34  import java.awt.event.ActionEvent;
    35  import java.awt.event.ActionListener;
    36  import java.awt.event.FocusEvent;
    37  import java.awt.event.FocusListener;
    38  import java.awt.event.InputEvent;
    39  import java.awt.event.KeyEvent;
    40  import java.awt.event.KeyListener;
    41  import java.awt.event.MouseAdapter;
    42  import java.awt.event.MouseEvent;
    43  import java.awt.event.MouseListener;
    44  import java.awt.event.MouseMotionAdapter;
    45  import java.awt.event.WindowAdapter;
    46  import java.awt.event.WindowEvent;
    47  import java.beans.PropertyChangeEvent;
    48  import java.beans.PropertyChangeListener;
    49  import java.io.EOFException;
    50  import java.io.File;
    51  import java.io.FileReader;
    52  import java.io.FileWriter;
    53  import java.io.IOException;
    54  import java.io.ObjectInputStream;
    55  import java.io.ObjectOutputStream;
    56  import java.io.StringReader;
    57  import java.io.UnsupportedEncodingException;
    58  import java.net.URLEncoder;
    59  import java.text.DateFormat;
    60  import java.text.NumberFormat;
    61  import java.text.SimpleDateFormat;
    62  import java.util.ArrayList;
    63  import java.util.Date;
    64  import java.util.Enumeration;
    65  import java.util.EventObject;
    66  import java.util.HashMap;
    67  import java.util.HashSet;
    68  import java.util.Iterator;
    69  import java.util.List;
    70  import java.util.Locale;
    71  import java.util.Map;
    72  import java.util.Set;
    73  import java.util.StringTokenizer;
    74  import java.util.Vector;
    75
    76  import javax.swing.AbstractAction;
    77  import javax.swing.AbstractListModel;
    78  import javax.swing.Action;
    79  import javax.swing.BorderFactory;
    80  import javax.swing.Box;
    81  import javax.swing.BoxLayout;
    82  import javax.swing.ButtonGroup;
    83  import javax.swing.ComboBoxEditor;
    84  import javax.swing.ImageIcon;
    85  import javax.swing.JButton;
    86  import javax.swing.JCheckBoxMenuItem;
    87  import javax.swing.JColorChooser;
    88  import javax.swing.JComboBox;
    89  import javax.swing.JComponent;
    90  import javax.swing.JDialog;
    91  import javax.swing.JEditorPane;
    92  import javax.swing.JFrame;
    93  import javax.swing.JLabel;
    94  import javax.swing.JMenuItem;
    95  import javax.swing.JPanel;
    96  import javax.swing.JPopupMenu;
    97  import javax.swing.JRadioButtonMenuItem;
    98  import javax.swing.JScrollPane;
    99  import javax.swing.JSeparator;
    100 import javax.swing.JSplitPane;
    101 import javax.swing.JTable;
    102 import javax.swing.JTextField;
    103 import javax.swing.JToolBar;
    104 import javax.swing.KeyStroke;
    105 import javax.swing.ListSelectionModel;
    106 import javax.swing.MutableComboBoxModel;
    107 import javax.swing.SwingConstants;
    108 import javax.swing.SwingUtilities;
    109 import javax.swing.UIManager;
    110 import javax.swing.WindowConstants;
    111 import javax.swing.event.CellEditorListener;
    112 import javax.swing.event.ChangeEvent;
    113 import javax.swing.event.DocumentEvent;
    114 import javax.swing.event.DocumentListener;
    115 import javax.swing.event.ListSelectionEvent;
    116 import javax.swing.event.ListSelectionListener;
    117 import javax.swing.event.PopupMenuEvent;
    118 import javax.swing.event.PopupMenuListener;
    119 import javax.swing.event.TableColumnModelEvent;
    120 import javax.swing.event.TableColumnModelListener;
    121 import javax.swing.event.TableModelEvent;
    122 import javax.swing.event.TableModelListener;
    123 import javax.swing.table.TableCellEditor;
    124 import javax.swing.table.TableColumn;
    125 import javax.swing.table.TableColumnModel;
    126 import javax.swing.text.Document;
    127
    128 import org.apache.log4j.Level;
    129 import org.apache.log4j.LogManager;
    130 import org.apache.log4j.Logger;
    131 import org.apache.log4j.PatternLayout;
    132 import org.apache.log4j.chainsaw.color.ColorPanel;
    133 import org.apache.log4j.chainsaw.color.RuleColorizer;
    134 import org.apache.log4j.chainsaw.filter.FilterModel;
    135 import org.apache.log4j.chainsaw.helper.SwingHelper;
    136 import org.apache.log4j.chainsaw.icons.ChainsawIcons;
    137 import org.apache.log4j.chainsaw.icons.LineIconFactory;
    138 import org.apache.log4j.chainsaw.layout.DefaultLayoutFactory;
    139 import org.apache.log4j.chainsaw.layout.EventDetailLayout;
    140 import org.apache.log4j.chainsaw.layout.LayoutEditorPane;
    141 import org.apache.log4j.chainsaw.messages.MessageCenter;
    142 import org.apache.log4j.chainsaw.prefs.LoadSettingsEvent;
    143 import org.apache.log4j.chainsaw.prefs.Profileable;
    144 import org.apache.log4j.chainsaw.prefs.SaveSettingsEvent;
    145 import org.apache.log4j.chainsaw.prefs.SettingsManager;
    146 import org.apache.log4j.chainsaw.xstream.TableColumnConverter;
    147 import org.apache.log4j.helpers.Constants;
    148 import org.apache.log4j.rule.ColorRule;
    149 import org.apache.log4j.rule.ExpressionRule;
    150 import org.apache.log4j.rule.Rule;
    151 import org.apache.log4j.spi.LoggingEvent;
    152 import org.apache.log4j.spi.LoggingEventFieldResolver;
    153
    154 import com.thoughtworks.xstream.XStream;
    155 import com.thoughtworks.xstream.io.xml.DomDriver;
    156
    157
    158 /**
    159  * A LogPanel provides a view to a collection of LoggingEvents.<br>
    160  * <br>
    161  * As events are received, the keywords in the 'tab identifier' application
    162  * preference  are replaced with the values from the received event.  The
    163  * main application uses  this expression to route received LoggingEvents to
    164  * individual LogPanels which  match each event's resolved expression.<br>
    165  * <br>
    166  * The LogPanel's capabilities can be broken up into four areas:<br>
    167  * <ul><li> toolbar - provides 'find' and 'refine focus' features
    168  * <li> logger tree - displays a tree of the logger hierarchy, which can be used
    169  * to filter the display
    170  * <li> table - displays the events which pass the filtering rules
    171  * <li>detail panel - displays information about the currently selected event
    172  * </ul>
    173  * Here is a complete list of LogPanel's capabilities:<br>
    174  * <ul><li>display selected LoggingEvent row number and total LoggingEvent count
    175  * <li>pause or unpause reception of LoggingEvents
    176  * <li>configure, load and save column settings (displayed columns, order, width)
    177  * <li>configure, load and save color rules
    178  * filter displayed LoggingEvents based on the logger tree settings
    179  * <li>filter displayed LoggingEvents based on a 'refine focus' expression
    180  * (evaluates only those LoggingEvents which pass the logger tree filter
    181  * <li>colorize LoggingEvents based on expressions
    182  * <li>hide, show and configure the detail pane and tooltip
    183  * <li>configure the formatting of the logger, level and timestamp fields
    184  * <li>dock or undock
    185  * <li>table displays first line of exception, but when cell is clicked, a
    186  * popup opens to display the full stack trace
    187  * <li>find
    188  * <li>scroll to bottom
    189  * <li>sort
    190  * <li>provide a context menu which can be used to build color or display expressions
    191  * <li>hide or show the logger tree
    192  * <li>toggle the container storing the LoggingEvents to use either a
    193  * CyclicBuffer (defaults to max size of 5000,  but configurable  through
    194  * CHAINSAW_CAPACITY system property) or ArrayList (no max size)
    195  * <li>use the mouse context menu to 'best-fit' columns, define display
    196  * expression filters based on mouse location and access other capabilities
    197  *</ul>
    198  *
    199  *@see org.apache.log4j.chainsaw.color.ColorPanel
    200  *@see org.apache.log4j.rule.ExpressionRule
    201  *@see org.apache.log4j.spi.LoggingEventFieldResolver
    202  *
    203  *@author Scott Deboy (sdeboy at apache.org)
    204  *@author Paul Smith (psmith at apache.org)
    205  *@author Stephen Pain
    206  *@author Isuru Suriarachchi
    207  *
    208  */
    209 public class LogPanel extends DockablePanel implements EventBatchListener, Profileable {
    210   private static final DateFormat TIMESTAMP_DATE_FORMAT = new SimpleDateFormat(Constants.TIMESTAMP_RULE_FORMAT);
    211   private static final double DEFAULT_DETAIL_SPLIT_LOCATION = 0.71d;
    212   private static final double DEFAULT_LOG_TREE_SPLIT_LOCATION = 0.2d;
    213   private final String identifier;
    214   private final ChainsawStatusBar statusBar;
    215   private final JFrame logPanelPreferencesFrame = new JFrame();
    216   private ColorPanel colorPanel;
    217   private final JFrame colorFrame = new JFrame();
    218   private final JFrame undockedFrame;
    219   private final DockablePanel externalPanel;
    220   private final Action dockingAction;
    221   private final JToolBar undockedToolbar;
    222   private final JSortTable table;
    223   private final TableColorizingRenderer renderer;
    224   private final EventContainer tableModel;
    225   private final JEditorPane detail;
    226   private final JSplitPane lowerPanel;
    227   private final DetailPaneUpdater detailPaneUpdater;
    228   private final JPanel detailPanel = new JPanel(new BorderLayout());
    229   private final JSplitPane nameTreeAndMainPanelSplit;
    230   private final LoggerNameTreePanel logTreePanel;
    231   private final LogPanelPreferenceModel preferenceModel = new LogPanelPreferenceModel();
    232   private ApplicationPreferenceModel applicationPreferenceModel;
    233   private final LogPanelPreferencePanel logPanelPreferencesPanel;
    234   private final FilterModel filterModel = new FilterModel();
    235   private final RuleColorizer colorizer = new RuleColorizer();
    236   private final RuleMediator tableRuleMediator = new RuleMediator(false);
    237   private final RuleMediator searchRuleMediator = new RuleMediator(true);
    238   private final EventDetailLayout detailLayout = new EventDetailLayout();
    239   private double lastLogTreePanelSplitLocation = DEFAULT_LOG_TREE_SPLIT_LOCATION;
    240   private Point currentPoint;
    241   private JTable currentTable;
    242   private boolean paused = false;
    243   private Rule findRule;
    244   private String currentFindRuleText;
    245   private Rule findMarkerRule;
    246   private final int dividerSize;
    247   static final String TABLE_COLUMN_ORDER = "table.columns.order";
    248   static final String TABLE_COLUMN_WIDTHS = "table.columns.widths";
    249   static final String COLORS_EXTENSION = ".colors";
    250   private static final int LOG_PANEL_SERIALIZATION_VERSION_NUMBER = 2; //increment when format changes
    251   private int previousLastIndex = -1;
    252   private final Logger logger = LogManager.getLogger(LogPanel.class);
    253   private AutoFilterComboBox filterCombo;
    254   private AutoFilterComboBox findCombo;
    255   private JScrollPane eventsPane;
    256   private int currentSearchMatchCount;
    257   private Rule clearTableExpressionRule;
    258   private int lowerPanelDividerLocation;
    259   private EventContainer searchModel;
    260   private final JSortTable searchTable;
    261   private TableColorizingRenderer searchRenderer;
    262   private ToggleToolTips mainToggleToolTips;
    263   private ToggleToolTips searchToggleToolTips;
    264   private JScrollPane detailPane;
    265   private JScrollPane searchPane;
    266   //only one tableCellEditor, shared by both tables
    267   private TableCellEditor markerCellEditor;
    268   private JToolBar detailToolbar;
    269   private boolean searchResultsDisplayed;
    270   private ColorizedEventAndSearchMatchThumbnail colorizedEventAndSearchMatchThumbnail;
    271   private EventTimeDeltaMatchThumbnail eventTimeDeltaMatchThumbnail;
    272   private boolean isDetailPanelVisible;
    273
    274   /**
    275    * Creates a new LogPanel object.  If a LogPanel with this identifier has
    276    * been loaded previously, reload settings saved on last exit.
    277    *
    278    * @param statusBar shared status bar, provided by main application
    279    * @param identifier used to load and save settings
    280    */
    281   public LogPanel(final ChainsawStatusBar statusBar, final String identifier, int cyclicBufferSize,
    282                   Map allColorizers, final ApplicationPreferenceModel applicationPreferenceModel) {
    283     this.identifier = identifier;
    284     this.statusBar = statusBar;
    285     this.applicationPreferenceModel = applicationPreferenceModel;
    286     this.logPanelPreferencesPanel = new LogPanelPreferencePanel(preferenceModel, applicationPreferenceModel);
    287     logger.debug("creating logpanel for " + identifier);
    288
    289     setLayout(new BorderLayout());
    290
    291     String prototypeValue = "1231231231231231231231";
    292
    293     filterCombo = new AutoFilterComboBox();
    294     findCombo = new AutoFilterComboBox();
    295
    296     filterCombo.setPrototypeDisplayValue(prototypeValue);
    297     buildCombo(filterCombo, true, findCombo.model);
    298
    299     findCombo.setPrototypeDisplayValue(prototypeValue);
    300     buildCombo(findCombo, false, filterCombo.model);
    301
    302     final Map columnNameKeywordMap = new HashMap();
    303     columnNameKeywordMap.put(ChainsawConstants.CLASS_COL_NAME, LoggingEventFieldResolver.CLASS_FIELD);
    304     columnNameKeywordMap.put(ChainsawConstants.FILE_COL_NAME, LoggingEventFieldResolver.FILE_FIELD);
    305     columnNameKeywordMap.put(ChainsawConstants.LEVEL_COL_NAME, LoggingEventFieldResolver.LEVEL_FIELD);
    306     columnNameKeywordMap.put(ChainsawConstants.LINE_COL_NAME, LoggingEventFieldResolver.LINE_FIELD);
    307     columnNameKeywordMap.put(ChainsawConstants.LOGGER_COL_NAME, LoggingEventFieldResolver.LOGGER_FIELD);
    308     columnNameKeywordMap.put(ChainsawConstants.NDC_COL_NAME, LoggingEventFieldResolver.NDC_FIELD);
    309     columnNameKeywordMap.put(ChainsawConstants.MESSAGE_COL_NAME, LoggingEventFieldResolver.MSG_FIELD);
    310     columnNameKeywordMap.put(ChainsawConstants.THREAD_COL_NAME, LoggingEventFieldResolver.THREAD_FIELD);
    311     columnNameKeywordMap.put(ChainsawConstants.THROWABLE_COL_NAME, LoggingEventFieldResolver.EXCEPTION_FIELD);
    312     columnNameKeywordMap.put(ChainsawConstants.TIMESTAMP_COL_NAME, LoggingEventFieldResolver.TIMESTAMP_FIELD);
    313     columnNameKeywordMap.put(ChainsawConstants.ID_COL_NAME.toUpperCase(), LoggingEventFieldResolver.PROP_FIELD + Constants.LOG4J_ID_KEY);
    314     columnNameKeywordMap.put(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE.toUpperCase(), LoggingEventFieldResolver.PROP_FIELD + ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
    315     columnNameKeywordMap.put(ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE.toUpperCase(), LoggingEventFieldResolver.PROP_FIELD + ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE);
    316
    317     logPanelPreferencesFrame.setTitle("'" + identifier + "' Log Panel Preferences");
    318     logPanelPreferencesFrame.setIconImage(
    319       ((ImageIcon) ChainsawIcons.ICON_PREFERENCES).getImage());
    320     logPanelPreferencesFrame.getContentPane().add(new JScrollPane(logPanelPreferencesPanel));
    321
    322     logPanelPreferencesFrame.setSize(740, 520);
    323
    324     logPanelPreferencesPanel.setOkCancelActionListener(
    325       new ActionListener() {
    326         public void actionPerformed(ActionEvent e) {
    327           logPanelPreferencesFrame.setVisible(false);
    328         }
    329       });
    330
    331         KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
    332             Action closeLogPanelPreferencesFrameAction = new AbstractAction() {
    333                 public void actionPerformed(ActionEvent e) {
    334                   logPanelPreferencesFrame.setVisible(false);
    335                 }
    336             };
    337             logPanelPreferencesFrame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "ESCAPE"); logPanelPreferencesFrame.getRootPane().
    338                     getActionMap().put("ESCAPE", closeLogPanelPreferencesFrameAction);
    339
    340
    341     setDetailPaneConversionPattern(
    342       DefaultLayoutFactory.getDefaultPatternLayout());
    343       detailLayout.setConversionPattern(
    344       DefaultLayoutFactory.getDefaultPatternLayout());
    345
    346     undockedFrame = new JFrame(identifier);
    347     undockedFrame.setDefaultCloseOperation(
    348       WindowConstants.DO_NOTHING_ON_CLOSE);
    349
    350     if (ChainsawIcons.UNDOCKED_ICON != null) {
    351       undockedFrame.setIconImage(
    352         new ImageIcon(ChainsawIcons.UNDOCKED_ICON).getImage());
    353     }
    354
    355     externalPanel = new DockablePanel();
    356     externalPanel.setLayout(new BorderLayout());
    357
    358     undockedFrame.addWindowListener(
    359       new WindowAdapter() {
    360         public void windowClosing(WindowEvent e) {
    361           dock();
    362         }
    363       });
    364
    365     undockedToolbar = createDockwindowToolbar();
    366     externalPanel.add(undockedToolbar, BorderLayout.NORTH);
    367     undockedFrame.getContentPane().add(externalPanel);
    368     undockedFrame.setSize(new Dimension(1024, 768));
    369     undockedFrame.pack();
    370
    371     preferenceModel.addPropertyChangeListener(
    372       "scrollToBottom",
    373       new PropertyChangeListener() {
    374         public void propertyChange(PropertyChangeEvent evt) {
    375           boolean value = ((Boolean) evt.getNewValue()).booleanValue();
    376           if (value) {
    377             scrollToBottom();
    378           }
    379         }
    380       });
    381     /*
    382      * Menus on which the preferencemodels rely
    383      */
    384
    385     /**
    386      * Setup a popup menu triggered for Timestamp column to allow time stamp
    387      * format changes
    388      */
    389     final JPopupMenu dateFormatChangePopup = new JPopupMenu();
    390     final JRadioButtonMenuItem isoButton =
    391       new JRadioButtonMenuItem(
    392         new AbstractAction("Use ISO8601Format") {
    393           public void actionPerformed(ActionEvent e) {
    394             preferenceModel.setDateFormatPattern("ISO8601");
    395           }
    396         });
    397     final JRadioButtonMenuItem simpleTimeButton =
    398       new JRadioButtonMenuItem(
    399         new AbstractAction("Use simple time") {
    400           public void actionPerformed(ActionEvent e) {
    401             preferenceModel.setDateFormatPattern("HH:mm:ss");
    402           }
    403         });
    404
    405     ButtonGroup dfBG = new ButtonGroup();
    406     dfBG.add(isoButton);
    407     dfBG.add(simpleTimeButton);
    408     simpleTimeButton.setSelected(true);
    409     dateFormatChangePopup.add(isoButton);
    410     dateFormatChangePopup.add(simpleTimeButton);
    411
    412     final JCheckBoxMenuItem menuItemLoggerTree =
    413       new JCheckBoxMenuItem("Show Logger Tree");
    414     menuItemLoggerTree.addActionListener(
    415       new ActionListener() {
    416         public void actionPerformed(ActionEvent e) {
    417           preferenceModel.setLogTreePanelVisible(
    418             menuItemLoggerTree.isSelected());
    419         }
    420       });
    421     menuItemLoggerTree.setIcon(new ImageIcon(ChainsawIcons.WINDOW_ICON));
    422
    423     final JCheckBoxMenuItem menuItemToggleDetails =
    424       new JCheckBoxMenuItem("Show Detail Pane");
    425     menuItemToggleDetails.addActionListener(
    426       new ActionListener() {
    427         public void actionPerformed(ActionEvent e) {
    428           preferenceModel.setDetailPaneVisible(
    429             menuItemToggleDetails.isSelected());
    430         }
    431       });
    432
    433     menuItemToggleDetails.setIcon(new ImageIcon(ChainsawIcons.INFO));
    434
    435     /*
    436      * add preferencemodel listeners
    437      */
    438     preferenceModel.addPropertyChangeListener("levelIcons",
    439       new PropertyChangeListener() {
    440         public void propertyChange(PropertyChangeEvent evt) {
    441           boolean useIcons = ((Boolean) evt.getNewValue()).booleanValue();
    442           renderer.setLevelUseIcons(useIcons);
    443           table.tableChanged(new TableModelEvent(tableModel));
    444           searchRenderer.setLevelUseIcons(useIcons);
    445           searchTable.tableChanged(new TableModelEvent(searchModel));
    446         }
    447       });
    448
    449     /*
    450      * add preferencemodel listeners
    451      */
    452     preferenceModel.addPropertyChangeListener("wrapMessage",
    453       new PropertyChangeListener() {
    454         public void propertyChange(PropertyChangeEvent evt) {
    455           boolean wrap = ((Boolean) evt.getNewValue()).booleanValue();
    456           renderer.setWrapMessage(wrap);
    457           table.tableChanged(new TableModelEvent(tableModel));
    458           searchRenderer.setWrapMessage(wrap);
    459           searchTable.tableChanged(new TableModelEvent(searchModel));
    460         }
    461       });
    462
    463     preferenceModel.addPropertyChangeListener("searchResultsVisible",
    464       new PropertyChangeListener() {
    465         public void propertyChange(PropertyChangeEvent evt) {
    466           boolean displaySearchResultsInDetailsIfAvailable = ((Boolean) evt.getNewValue()).booleanValue();
    467           if (displaySearchResultsInDetailsIfAvailable) {
    468             showSearchResults();
    469           } else {
    470             hideSearchResults();
    471           }
    472         }
    473       });
    474
    475       preferenceModel.addPropertyChangeListener("highlightSearchMatchText",
    476         new PropertyChangeListener() {
    477           public void propertyChange(PropertyChangeEvent evt) {
    478             boolean highlightText = ((Boolean) evt.getNewValue()).booleanValue();
    479             renderer.setHighlightSearchMatchText(highlightText);
    480             table.tableChanged(new TableModelEvent(tableModel));
    481             searchRenderer.setHighlightSearchMatchText(highlightText);
    482             searchTable.tableChanged(new TableModelEvent(searchModel));
    483           }
    484         });
    485
    486     preferenceModel.addPropertyChangeListener(
    487       "detailPaneVisible",
    488       new PropertyChangeListener() {
    489         public void propertyChange(PropertyChangeEvent evt) {
    490           boolean detailPaneVisible = ((Boolean) evt.getNewValue()).booleanValue();
    491
    492           if (detailPaneVisible) {
    493             showDetailPane();
    494           } else {
    495             //don't hide the detail pane if search results are being displayed
    496             if (!searchResultsDisplayed) {
    497               hideDetailPane();
    498             }
    499           }
    500         }
    501       });
    502
    503     preferenceModel.addPropertyChangeListener(
    504       "logTreePanelVisible",
    505       new PropertyChangeListener() {
    506         public void propertyChange(PropertyChangeEvent evt) {
    507           boolean newValue = ((Boolean) evt.getNewValue()).booleanValue();
    508
    509           if (newValue) {
    510             showLogTreePanel();
    511           } else {
    512             hideLogTreePanel();
    513           }
    514         }
    515       });
    516
    517     preferenceModel.addPropertyChangeListener("toolTips",
    518       new PropertyChangeListener() {
    519         public void propertyChange(PropertyChangeEvent evt) {
    520           boolean toolTips = ((Boolean) evt.getNewValue()).booleanValue();
    521           renderer.setToolTipsVisible(toolTips);
    522           searchRenderer.setToolTipsVisible(toolTips);
    523         }
    524       });
    525
    526     preferenceModel.addPropertyChangeListener("visibleColumns",
    527       new PropertyChangeListener() {
    528     	public void propertyChange(PropertyChangeEvent evt) {
    529     		//remove all columns and re-add visible
    530             TableColumnModel columnModel = table.getColumnModel();
    531             while (columnModel.getColumnCount() > 0) {
    532                 columnModel.removeColumn(columnModel.getColumn(0));
    533         		}
    534             for (Iterator iter = preferenceModel.getVisibleColumnOrder().iterator();iter.hasNext();) {
    535               TableColumn c = (TableColumn)iter.next();
    536               if (c.getHeaderValue().toString().equalsIgnoreCase(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE))
    537               {
    538                 c.setCellEditor(markerCellEditor);
    539               }
    540               columnModel.addColumn(c);
    541         		}
    542           TableColumnModel searchColumnModel = searchTable.getColumnModel();
    543           while (searchColumnModel.getColumnCount() > 0) {
    544               searchColumnModel.removeColumn(searchColumnModel.getColumn(0));
    545           }
    546           for (Iterator iter = preferenceModel.getVisibleColumnOrder().iterator();iter.hasNext();) {
    547             TableColumn c = (TableColumn)iter.next();
    548             searchColumnModel.addColumn(c);
    549           }
    550       	}
    551       });
    552
    553     PropertyChangeListener datePrefsChangeListener =
    554       new PropertyChangeListener() {
    555         public void propertyChange(PropertyChangeEvent evt) {
    556           LogPanelPreferenceModel model = (LogPanelPreferenceModel) evt.getSource();
    557
    558           isoButton.setSelected(model.isUseISO8601Format());
    559           simpleTimeButton.setSelected(!model.isUseISO8601Format() && !model.isCustomDateFormat());
    560
    561           if (model.getTimeZone() != null) {
    562             renderer.setTimeZone(model.getTimeZone());
    563             searchRenderer.setTimeZone(model.getTimeZone());
    564           }
    565
    566           if (model.isUseISO8601Format()) {
    567             renderer.setDateFormatter(new SimpleDateFormat(Constants.ISO8601_PATTERN));
    568             searchRenderer.setDateFormatter(new SimpleDateFormat(Constants.ISO8601_PATTERN));
    569           } else {
    570       		try {
    571               renderer.setDateFormatter(new SimpleDateFormat(model.getDateFormatPattern()));
    572           } catch (IllegalArgumentException iae) {
    573             model.setDefaultDatePatternFormat();
    574             renderer.setDateFormatter(new SimpleDateFormat(Constants.ISO8601_PATTERN));
    575           }
    576   		    try {
    577               searchRenderer.setDateFormatter(new SimpleDateFormat(model.getDateFormatPattern()));
    578           } catch (IllegalArgumentException iae) {
    579             model.setDefaultDatePatternFormat();
    580             searchRenderer.setDateFormatter(new SimpleDateFormat(Constants.ISO8601_PATTERN));
    581           }
    582         }
    583
    584         table.tableChanged(new TableModelEvent(tableModel));
    585         searchTable.tableChanged(new TableModelEvent(searchModel));
    586         }
    587       };
    588
    589     preferenceModel.addPropertyChangeListener("dateFormatPattern", datePrefsChangeListener);
    590     preferenceModel.addPropertyChangeListener("dateFormatTimeZone", datePrefsChangeListener);
    591
    592     preferenceModel.addPropertyChangeListener("clearTableExpression", new PropertyChangeListener() {
    593         public void propertyChange(PropertyChangeEvent evt) {
    594             LogPanelPreferenceModel model = (LogPanelPreferenceModel)evt.getSource();
    595             String expression = model.getClearTableExpression();
    596             try {
    597                 clearTableExpressionRule = ExpressionRule.getRule(expression);
    598                 logger.info("clearTableExpressionRule set to: " + expression);
    599             } catch (Exception e) {
    600                 logger.info("clearTableExpressionRule invalid - ignoring: " + expression);
    601                 clearTableExpressionRule = null;
    602             }
    603         }
    604     });
    605
    606     preferenceModel.addPropertyChangeListener("loggerPrecision",
    607       new PropertyChangeListener() {
    608         public void propertyChange(PropertyChangeEvent evt) {
    609           LogPanelPreferenceModel model = (LogPanelPreferenceModel) evt.getSource();
    610
    611           renderer.setLoggerPrecision(model.getLoggerPrecision());
    612           table.tableChanged(new TableModelEvent(tableModel));
    613
    614           searchRenderer.setLoggerPrecision(model.getLoggerPrecision());
    615           searchTable.tableChanged(new TableModelEvent(searchModel));
    616         }
    617       });
    618
    619     preferenceModel.addPropertyChangeListener("toolTips",
    620       new PropertyChangeListener() {
    621         public void propertyChange(PropertyChangeEvent evt) {
    622           boolean value = ((Boolean) evt.getNewValue()).booleanValue();
    623           searchToggleToolTips.setSelected(value);
    624           mainToggleToolTips.setSelected(value);
    625         }
    626       });
    627
    628     preferenceModel.addPropertyChangeListener(
    629       "logTreePanelVisible",
    630       new PropertyChangeListener() {
    631         public void propertyChange(PropertyChangeEvent evt) {
    632           boolean value = ((Boolean) evt.getNewValue()).booleanValue();
    633           menuItemLoggerTree.setSelected(value);
    634         }
    635       });
    636
    637     preferenceModel.addPropertyChangeListener(
    638       "detailPaneVisible",
    639       new PropertyChangeListener() {
    640         public void propertyChange(PropertyChangeEvent evt) {
    641           boolean value = ((Boolean) evt.getNewValue()).booleanValue();
    642           menuItemToggleDetails.setSelected(value);
    643         }
    644       });
    645
    646     applicationPreferenceModel.addPropertyChangeListener("searchColor", new PropertyChangeListener() {
    647         public void propertyChange(PropertyChangeEvent evt)
    648         {
    649             if (table != null) {
    650               table.repaint();
    651             }
    652             if (searchTable != null) {
    653               searchTable.repaint();
    654             }
    655         }
    656     });
    657
    658     applicationPreferenceModel.addPropertyChangeListener("alternatingColor", new PropertyChangeListener() {
    659         public void propertyChange(PropertyChangeEvent evt)
    660         {
    661             if (table != null) {
    662               table.repaint();
    663             }
    664             if (searchTable != null) {
    665               searchTable.repaint();
    666            }
    667         }
    668     });
    669
    670     /*
    671      *End of preferenceModel listeners
    672      */
    673     tableModel = new ChainsawCyclicBufferTableModel(cyclicBufferSize, colorizer, "main");
    674     table = new JSortTable(tableModel);
    675
    676     markerCellEditor = new MarkerCellEditor();
    677     table.setName("main");
    678     table.setColumnSelectionAllowed(false);
    679     table.setRowSelectionAllowed(true);
    680
    681     searchModel = new ChainsawCyclicBufferTableModel(cyclicBufferSize, colorizer, "search");
    682     searchTable = new JSortTable(searchModel);
    683
    684     searchTable.setName("search");
    685     searchTable.setColumnSelectionAllowed(false);
    686     searchTable.setRowSelectionAllowed(true);
    687
    688     //we've mapped f2, shift f2 and ctrl-f2 to marker-related actions, unmap them from the table
    689     table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("F2"), "none");
    690     table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, InputEvent.SHIFT_MASK), "none");
    691     table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "none");
    692     table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | InputEvent.SHIFT_MASK), "none");
    693
    694     //we're also mapping ctrl-a to scroll-to-top, unmap from the table
    695     table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "none");
    696
    697     searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("F2"), "none");
    698     searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, InputEvent.SHIFT_MASK), "none");
    699     searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "none");
    700     searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | InputEvent.SHIFT_MASK), "none");
    701
    702     //we're also mapping ctrl-a to scroll-to-top, unmap from the table
    703     searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "none");
    704
    705     //add a listener to update the 'refine focus'
    706     tableModel.addNewKeyListener(new NewKeyListener() {
    707 		public void newKeyAdded(NewKeyEvent e) {
    708           columnNameKeywordMap.put(e.getKey(), "PROP." + e.getKey());
    709 		}
    710     });
    711
    712     /*
    713      * Set the Display rule to use the mediator, the model will add itself as
    714      * a property change listener and update itself when the rule changes.
    715      */
    716     tableModel.setRuleMediator(tableRuleMediator);
    717     searchModel.setRuleMediator(searchRuleMediator);
    718
    719     tableModel.addEventCountListener(
    720       new EventCountListener() {
    721         public void eventCountChanged(int currentCount, int totalCount) {
    722           if (LogPanel.this.isVisible()) {
    723             statusBar.setSelectedLine(
    724               table.getSelectedRow() + 1, currentCount, totalCount, getIdentifier());
    725           }
    726         }
    727       });
    728
    729     tableModel.addEventCountListener(
    730       new EventCountListener() {
    731         final NumberFormat formatter = NumberFormat.getPercentInstance();
    732         boolean warning75 = false;
    733         boolean warning100 = false;
    734
    735         public void eventCountChanged(int currentCount, int totalCount) {
    736           if (preferenceModel.isCyclic()) {
    737             double percent =
    738               ((double) totalCount) / ((ChainsawCyclicBufferTableModel) tableModel)
    739               .getMaxSize();
    740             String msg = null;
    741             boolean wasWarning = warning75 || warning100;
    742             if ((percent > 0.75) && (percent < 1.0) && !warning75) {
    743               msg =
    744                 "Warning :: " + formatter.format(percent) + " of the '"
    745                 + getIdentifier() + "' buffer has been used";
    746               warning75 = true;
    747             } else if ((percent >= 1.0) && !warning100) {
    748               msg =
    749                 "Warning :: " + formatter.format(percent) + " of the '"
    750                 + getIdentifier()
    751                 + "' buffer has been used.  Older events are being discarded.";
    752               warning100 = true;
    753             } else {
    754                 //clear msg
    755                 msg = "";
    756                 warning75 = false;
    757                 warning100 = false;
    758             }
    759
    760             if (msg != null && wasWarning) {
    761               MessageCenter.getInstance().getLogger().info(msg);
    762             }
    763           }
    764         }
    765       });
    766
    767     /*
    768      * Logger tree panel
    769      *
    770      */
    771     LogPanelLoggerTreeModel logTreeModel = new LogPanelLoggerTreeModel();
    772     logTreePanel = new LoggerNameTreePanel(logTreeModel, preferenceModel, this, colorizer, filterModel);
    773     logTreePanel.getLoggerVisibilityRule().addPropertyChangeListener(new PropertyChangeListener()
    774     {
    775         public void propertyChange(PropertyChangeEvent evt)
    776         {
    777             if (evt.getPropertyName().equals("searchExpression")) {
    778                 findCombo.setSelectedItem(evt.getNewValue().toString());
    779                 findNext();
    780             }
    781         }
    782     });
    783
    784     tableModel.addLoggerNameListener(logTreeModel);
    785     tableModel.addLoggerNameListener(logTreePanel);
    786
    787     /**
    788      * Set the LoggerRule to be the LoggerTreePanel, as this visual component
    789      * is a rule itself, and the RuleMediator will automatically listen when
    790      * it's rule state changes.
    791      */
    792     tableRuleMediator.setLoggerRule(logTreePanel.getLoggerVisibilityRule());
    793     searchRuleMediator.setLoggerRule(logTreePanel.getLoggerVisibilityRule());
    794
    795     colorizer.setLoggerRule(logTreePanel.getLoggerColorRule());
    796
    797     /*
    798      * Color rule frame and panel
    799      */
    800     colorFrame.setTitle("'" + identifier + "' color settings");
    801     colorFrame.setIconImage(
    802       ((ImageIcon) ChainsawIcons.ICON_PREFERENCES).getImage());
    803
    804     allColorizers.put(identifier, colorizer);
    805     colorPanel = new ColorPanel(colorizer, filterModel, allColorizers, applicationPreferenceModel);
    806
    807     colorFrame.getContentPane().add(colorPanel);
    808
    809         Action closeColorPanelAction = new AbstractAction() {
    810             public void actionPerformed(ActionEvent e) {
    811               colorPanel.hidePanel();
    812             }
    813         };
    814         colorFrame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "ESCAPE"); colorFrame.getRootPane().
    815                 getActionMap().put("ESCAPE", closeColorPanelAction);
    816
    817     colorPanel.setCloseActionListener(
    818       new ActionListener() {
    819         public void actionPerformed(ActionEvent e) {
    820           colorFrame.setVisible(false);
    821         }
    822       });
    823
    824     colorizer.addPropertyChangeListener(
    825       "colorrule",
    826       new PropertyChangeListener() {
    827         public void propertyChange(PropertyChangeEvent evt) {
    828           for (Iterator iter = tableModel.getAllEvents().iterator();iter.hasNext();) {
    829             LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper)iter.next();
    830             loggingEventWrapper.updateColorRuleColors(colorizer.getBackgroundColor(loggingEventWrapper.getLoggingEvent()), colorizer.getForegroundColor(loggingEventWrapper.getLoggingEvent()));
    831           }
    832 //          no need to update searchmodel events since tablemodel and searchmodel share all events, and color rules aren't different between the two
    833 //          if that changes, un-do the color syncing in loggingeventwrapper & re-enable this code
    834 //
    835 //          for (Iterator iter = searchModel.getAllEvents().iterator();iter.hasNext();) {
    836 //             LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper)iter.next();
    837 //             loggingEventWrapper.updateColorRuleColors(colorizer.getBackgroundColor(loggingEventWrapper.getLoggingEvent()), colorizer.getForegroundColor(loggingEventWrapper.getLoggingEvent()));
    838 //           }
    839           colorizedEventAndSearchMatchThumbnail.configureColors();
    840           lowerPanel.revalidate();
    841           lowerPanel.repaint();
    842
    843           searchTable.revalidate();
    844           searchTable.repaint();
    845         }
    846       });
    847
    848     /*
    849      * Table definition.  Actual construction is above (next to tablemodel)
    850      */
    851     table.setRowHeight(ChainsawConstants.DEFAULT_ROW_HEIGHT);
    852     table.setRowMargin(0);
    853     table.getColumnModel().setColumnMargin(0);
    854     table.setShowGrid(false);
    855     table.getColumnModel().addColumnModelListener(new ChainsawTableColumnModelListener(table));
    856     table.setAutoCreateColumnsFromModel(false);
    857     table.addMouseMotionListener(new TableColumnDetailMouseListener(table, tableModel));
    858     table.addMouseListener(new TableMarkerListener(table, tableModel, searchModel));
    859     table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    860
    861     searchTable.setRowHeight(ChainsawConstants.DEFAULT_ROW_HEIGHT);
    862     searchTable.setRowMargin(0);
    863     searchTable.getColumnModel().setColumnMargin(0);
    864     searchTable.setShowGrid(false);
    865     searchTable.getColumnModel().addColumnModelListener(new ChainsawTableColumnModelListener(searchTable));
    866     searchTable.setAutoCreateColumnsFromModel(false);
    867     searchTable.addMouseMotionListener(new TableColumnDetailMouseListener(searchTable, searchModel));
    868     searchTable.addMouseListener(new TableMarkerListener(searchTable, searchModel, tableModel));
    869     searchTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    870
    871
    872     //set valueisadjusting if holding down a key - don't process setdetail events
    873     table.addKeyListener(
    874       new KeyListener() {
    875         public void keyTyped(KeyEvent e) {
    876         }
    877
    878         public void keyPressed(KeyEvent e) {
    879           synchronized (detail) {
    880             table.getSelectionModel().setValueIsAdjusting(true);
    881             detail.notify();
    882           }
    883         }
    884
    885         public void keyReleased(KeyEvent e) {
    886           synchronized (detail) {
    887             table.getSelectionModel().setValueIsAdjusting(false);
    888             detail.notify();
    889           }
    890         }
    891       });
    892
    893     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    894     searchTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    895
    896     table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
    897         public void valueChanged(ListSelectionEvent evt) {
    898             if (((evt.getFirstIndex() == evt.getLastIndex())
    899                 && (evt.getFirstIndex() > 0) && previousLastIndex != -1) || (evt.getValueIsAdjusting())) {
    900               return;
    901             }
    902             boolean lastIndexOnLastRow = (evt.getLastIndex() == (table.getRowCount() - 1));
    903             boolean lastIndexSame = (previousLastIndex == evt.getLastIndex());
    904
    905             /*
    906              * when scroll-to-bottom is active, here is what events look like:
    907              * rowcount-1: 227, last: 227, previous last: 191..first: 191
    908              *
    909              * when the user has unselected the bottom row, here is what the events look like:
    910              * rowcount-1: 227, last: 227, previous last: 227..first: 222
    911              *
    912              * note: previouslast is set after it is evaluated in the bypass scroll check
    913             */
    914            //System.out.println("rowcount: " + (table.getRowCount() - 1) + ", last: " + evt.getLastIndex() +", previous last: " + previousLastIndex + "..first: " + evt.getFirstIndex() + ", isadjusting: " + evt.getValueIsAdjusting());
    915
    916             boolean disableScrollToBottom = (lastIndexOnLastRow && lastIndexSame && previousLastIndex != evt.getFirstIndex());
    917             if (disableScrollToBottom && isScrollToBottom() && table.getRowCount() > 0) {
    918               preferenceModel.setScrollToBottom(false);
    919             }
    920             previousLastIndex = evt.getLastIndex();
    921           }
    922         }
    923     );
    924
    925     table.getSelectionModel().addListSelectionListener(
    926       new ListSelectionListener() {
    927         public void valueChanged(ListSelectionEvent evt) {
    928           if (((evt.getFirstIndex() == evt.getLastIndex())
    929               && (evt.getFirstIndex() > 0) && previousLastIndex != -1) || (evt.getValueIsAdjusting())) {
    930             return;
    931           }
    932
    933           final ListSelectionModel lsm = (ListSelectionModel) evt.getSource();
    934
    935           if (lsm.isSelectionEmpty()) {
    936             if (isVisible()) {
    937               statusBar.setNothingSelected();
    938             }
    939
    940             if (detail.getDocument().getDefaultRootElement() != null) {
    941               detailPaneUpdater.setSelectedRow(-1);
    942             }
    943           } else {
    944             if (table.getSelectedRow() > -1) {
    945               int selectedRow = table.getSelectedRow();
    946
    947               if (isVisible()) {
    948                 updateStatusBar();
    949               }
    950
    951               try {
    952                 if (tableModel.getRowCount() >= selectedRow) {
    953                   detailPaneUpdater.setSelectedRow(table.getSelectedRow());
    954                 } else {
    955                   detailPaneUpdater.setSelectedRow(-1);
    956                 }
    957               } catch (Exception e) {
    958                 e.printStackTrace();
    959                 detailPaneUpdater.setSelectedRow(-1);
    960               }
    961             }
    962           }
    963         }
    964       });
    965
    966     renderer = new TableColorizingRenderer(colorizer, applicationPreferenceModel, tableModel, preferenceModel, true);
    967     renderer.setToolTipsVisible(preferenceModel.isToolTips());
    968
    969     table.setDefaultRenderer(Object.class, renderer);
    970
    971     searchRenderer = new TableColorizingRenderer(colorizer, applicationPreferenceModel, searchModel, preferenceModel, false);
    972     searchRenderer.setToolTipsVisible(preferenceModel.isToolTips());
    973
    974     searchTable.setDefaultRenderer(Object.class, searchRenderer);
    975
    976     /*
    977      * Throwable popup
    978      */
    979     table.addMouseListener(new ThrowableDisplayMouseAdapter(table, tableModel));
    980     searchTable.addMouseListener(new ThrowableDisplayMouseAdapter(searchTable, searchModel));
    981
    982     //select a row in the main table when a row in the search table is selected
    983     searchTable.addMouseListener(new MouseAdapter() {
    984       public void mouseClicked(MouseEvent e) {
    985         LoggingEventWrapper loggingEventWrapper = searchModel.getRow(searchTable.getSelectedRow());
    986         if (loggingEventWrapper != null) {
    987           int id = new Integer(loggingEventWrapper.getLoggingEvent().getProperty("log4jid")).intValue();
    988           //preserve the table's viewble column
    989           setSelectedEvent(id);
    990         }
    991       }
    992     });
    993
    994     /*
    995      * We listen for new Key's coming in so we can get them automatically
    996      * added as columns
    997      */
    998     tableModel.addNewKeyListener(
    999       new NewKeyListener() {
    1000         public void newKeyAdded(final NewKeyEvent e) {
    1001         	SwingHelper.invokeOnEDT(new Runnable() {
    1002         		public void run() {
    1003            // don't add the column if we already know about it, this could be if we've seen it before and saved the column preferences
    1004             //this may throw an illegalargexception - ignore it because we need to add only if not already added
    1005         	//if the column is already added, don't add again
    1006
    1007         	try {
    1008         	if(table.getColumn(e.getKey())!=null){
    1009                 return;
    1010             }
    1011             //no need to check search table - we use the same columns
    1012         	} catch (IllegalArgumentException iae) {}
    1013           TableColumn col = new TableColumn(e.getNewModelIndex());
    1014           col.setHeaderValue(e.getKey());
    1015
    1016           if (preferenceModel.addColumn(col)) {
    1017             if (preferenceModel.isColumnVisible(col) || !applicationPreferenceModel.isDefaultColumnsSet() || applicationPreferenceModel.isDefaultColumnsSet() &&
    1018                 applicationPreferenceModel.getDefaultColumnNames().contains(col.getHeaderValue())) {
    1019               table.addColumn(col);
    1020                 searchTable.addColumn(col);
    1021           	  preferenceModel.setColumnVisible(e.getKey().toString(), true);
    1022             }
    1023           }
    1024         		}
    1025         	});
    1026         }
    1027       });
    1028
    1029     //if the table is refiltered, try to reselect the last selected row
    1030     //refilter with a newValue of TRUE means refiltering is about to begin
    1031     //refilter with a newValue of FALSE means refiltering is complete
    1032     //assuming notification is called on the EDT so we can in the current EDT call update the scroll & selection
    1033     tableModel.addPropertyChangeListener("refilter", new PropertyChangeListener() {
    1034         private LoggingEventWrapper currentEvent;
    1035         public void propertyChange(PropertyChangeEvent evt) {
    1036             //if new value is true, filtering is about to begin
    1037             //if new value is false, filtering is complete
    1038             if (evt.getNewValue().equals(Boolean.TRUE)) {
    1039                 int currentRow = table.getSelectedRow();
    1040                 if (currentRow > -1) {
    1041                     currentEvent = tableModel.getRow(currentRow);
    1042                 }
    1043             } else {
    1044                 if (currentEvent != null) {
    1045                     table.scrollToRow(tableModel.getRowIndex(currentEvent));
    1046                 }
    1047             }
    1048         }
    1049     });
    1050
    1051     table.getTableHeader().addMouseListener(
    1052       new MouseAdapter() {
    1053         public void mouseClicked(MouseEvent e) {
    1054           checkEvent(e);
    1055         }
    1056
    1057         public void mousePressed(MouseEvent e) {
    1058           checkEvent(e);
    1059         }
    1060
    1061         public void mouseReleased(MouseEvent e) {
    1062           checkEvent(e);
    1063         }
    1064
    1065         private void checkEvent(MouseEvent e) {
    1066           if (e.isPopupTrigger()) {
    1067             TableColumnModel colModel = table.getColumnModel();
    1068             int index = colModel.getColumnIndexAtX(e.getX());
    1069             int modelIndex = colModel.getColumn(index).getModelIndex();
    1070
    1071             if ((modelIndex + 1) == ChainsawColumns.INDEX_TIMESTAMP_COL_NAME) {
    1072               dateFormatChangePopup.show(e.getComponent(), e.getX(), e.getY());
    1073             }
    1074           }
    1075         }
    1076       });
    1077
    1078     /*
    1079      * Upper panel definition
    1080      */
    1081     JPanel upperPanel = new JPanel();
    1082     upperPanel.setLayout(new BoxLayout(upperPanel, BoxLayout.X_AXIS));
    1083     upperPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 0));
    1084
    1085     final JLabel filterLabel = new JLabel("Refine focus on: ");
    1086     filterLabel.setFont(filterLabel.getFont().deriveFont(Font.BOLD));
    1087
    1088     upperPanel.add(filterLabel);
    1089     upperPanel.add(Box.createHorizontalStrut(3));
    1090     upperPanel.add(filterCombo);
    1091     upperPanel.add(Box.createHorizontalStrut(3));
    1092
    1093     final JTextField filterText =(JTextField) filterCombo.getEditor().getEditorComponent();
    1094     final JTextField findText =(JTextField) findCombo.getEditor().getEditorComponent();
    1095
    1096
    1097     //Adding a button to clear filter expressions which are currently remembered by Chainsaw...
    1098     final JButton removeFilterButton = new JButton(" Remove ");
    1099
    1100     removeFilterButton.setToolTipText("Click here to remove the selected expression from the list");
    1101     removeFilterButton.addActionListener(
    1102             new AbstractAction() {
    1103                 public void actionPerformed(ActionEvent e){
    1104                 	Object selectedItem = filterCombo.getSelectedItem();
    1105                     if (e.getSource() == removeFilterButton && selectedItem != null && !selectedItem.toString().trim().equals("")){
    1106                       //don't just remove the entry from the store, clear the field
    1107                       int index = filterCombo.getSelectedIndex();
    1108                       filterText.setText(null);
    1109                       filterCombo.setSelectedIndex(-1);
    1110                       filterCombo.removeItemAt(index);
    1111                       if (!(findCombo.getSelectedItem() != null && findCombo.getSelectedItem().equals(selectedItem))) {
    1112                         //now remove the entry from the other model
    1113                         ((AutoFilterComboBox.AutoFilterComboBoxModel)findCombo.getModel()).removeElement(selectedItem);
    1114                       }
    1115                     }
    1116                 }
    1117             }
    1118     );
    1119     upperPanel.add(removeFilterButton);
    1120     //add some space between refine focus and search sections of the panel
    1121     upperPanel.add(Box.createHorizontalStrut(25));
    1122
    1123     final JLabel findLabel = new JLabel("Find: ");
    1124     findLabel.setFont(filterLabel.getFont().deriveFont(Font.BOLD));
    1125
    1126     upperPanel.add(findLabel);
    1127     upperPanel.add(Box.createHorizontalStrut(3));
    1128
    1129     upperPanel.add(findCombo);
    1130     upperPanel.add(Box.createHorizontalStrut(3));
    1131
    1132     Action findNextAction = getFindNextAction();
    1133     Action findPreviousAction = getFindPreviousAction();
    1134     //add up & down search
    1135     JButton findNextButton = new SmallButton(findNextAction);
    1136     findNextButton.setText("");
    1137     findNextButton.getActionMap().put(
    1138       findNextAction.getValue(Action.NAME), findNextAction);
    1139     findNextButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
    1140       (KeyStroke) findNextAction.getValue(Action.ACCELERATOR_KEY),
    1141       findNextAction.getValue(Action.NAME));
    1142
    1143     JButton findPreviousButton = new SmallButton(findPreviousAction);
    1144     findPreviousButton.setText("");
    1145     findPreviousButton.getActionMap().put(
    1146       findPreviousAction.getValue(Action.NAME), findPreviousAction);
    1147     findPreviousButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
    1148       (KeyStroke) findPreviousAction.getValue(Action.ACCELERATOR_KEY),
    1149       findPreviousAction.getValue(Action.NAME));
    1150
    1151     upperPanel.add(findNextButton);
    1152
    1153     upperPanel.add(findPreviousButton);
    1154     upperPanel.add(Box.createHorizontalStrut(3));
    1155
    1156     //Adding a button to clear filter expressions which are currently remembered by Chainsaw...
    1157     final JButton removeFindButton = new JButton(" Remove ");
    1158     removeFindButton.setToolTipText("Click here to remove the selected expression from the list");
    1159     removeFindButton.addActionListener(
    1160             new AbstractAction() {
    1161                 public void actionPerformed(ActionEvent e){
    1162                 	Object selectedItem = findCombo.getSelectedItem();
    1163                     if (e.getSource() == removeFindButton && selectedItem != null && !selectedItem.toString().trim().equals("")){
    1164                       //don't just remove the entry from the store, clear the field
    1165                       int index = findCombo.getSelectedIndex();
    1166                       findText.setText(null);
    1167                       findCombo.setSelectedIndex(-1);
    1168                       findCombo.removeItemAt(index);
    1169                       if (!(filterCombo.getSelectedItem() != null && filterCombo.getSelectedItem().equals(selectedItem))) {
    1170                         //now remove the entry from the other model if it wasn't selected
    1171                         ((AutoFilterComboBox.AutoFilterComboBoxModel)filterCombo.getModel()).removeElement(selectedItem);
    1172                       }
    1173                     }
    1174                 }
    1175             }
    1176     );
    1177     upperPanel.add(removeFindButton);
    1178
    1179     //define search and refine focus selection and clear actions
    1180     Action findFocusAction = new AbstractAction() {
    1181       public void actionPerformed(ActionEvent actionEvent) {
    1182         findCombo.requestFocus();
    1183       }
    1184     };
    1185
    1186     Action filterFocusAction = new AbstractAction() {
    1187       public void actionPerformed(ActionEvent actionEvent) {
    1188         filterCombo.requestFocus();
    1189       }
    1190     };
    1191
    1192     Action findClearAction = new AbstractAction() {
    1193       public void actionPerformed(ActionEvent actionEvent) {
    1194         findCombo.setSelectedIndex(-1);
    1195         findNext();
    1196       }
    1197     };
    1198
    1199     Action filterClearAction = new AbstractAction() {
    1200       public void actionPerformed(ActionEvent actionEvent) {
    1201         setRefineFocusText("");
    1202         filterCombo.refilter();
    1203       }
    1204     };
    1205
    1206     //now add them to the action and input maps for the logpanel
    1207         KeyStroke ksFindFocus =
    1208       KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    1209     KeyStroke ksFilterFocus =
    1210       KeyStroke.getKeyStroke(KeyEvent.VK_R, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    1211         KeyStroke ksFindClear =
    1212       KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.SHIFT_MASK |Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    1213     KeyStroke ksFilterClear =
    1214       KeyStroke.getKeyStroke(KeyEvent.VK_R,  InputEvent.SHIFT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    1215
    1216     getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksFindFocus, "FindFocus");
    1217     getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksFilterFocus, "FilterFocus");
    1218     getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksFindClear, "FindClear");
    1219     getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksFilterClear, "FilterClear");
    1220
    1221     getActionMap().put("FindFocus", findFocusAction);
    1222     getActionMap().put("FilterFocus", filterFocusAction);
    1223     getActionMap().put("FindClear", findClearAction);
    1224     getActionMap().put("FilterClear", filterClearAction);
    1225
    1226     /*
    1227      * Detail pane definition
    1228      */
    1229     detail = new JEditorPane(ChainsawConstants.DETAIL_CONTENT_TYPE, "");
    1230     detail.setEditable(false);
    1231
    1232     detailPaneUpdater = new DetailPaneUpdater();
    1233
    1234     //if the panel gets focus, update the detail pane
    1235     addFocusListener(new FocusListener() {
    1236
    1237         public void focusGained(FocusEvent e) {
    1238             detailPaneUpdater.updateDetailPane();
    1239         }
    1240
    1241         public void focusLost(FocusEvent e) {
    1242
    1243         }
    1244     });
    1245     findMarkerRule = ExpressionRule.getRule("prop." + ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE + " exists");
    1246
    1247     tableModel.addTableModelListener(new TableModelListener() {
    1248 		public void tableChanged(TableModelEvent e) {
    1249             int currentRow = table.getSelectedRow();
    1250             if (e.getFirstRow() <= currentRow && e.getLastRow() >= currentRow) {
    1251                 //current row has changed - update
    1252                 detailPaneUpdater.setAndUpdateSelectedRow(table.getSelectedRow());
    1253             }
    1254 		}
    1255     });
    1256     addPropertyChangeListener("detailPaneConversionPattern", detailPaneUpdater);
    1257
    1258     searchPane = new JScrollPane(searchTable);
    1259     searchPane.getVerticalScrollBar().setUnitIncrement(ChainsawConstants.DEFAULT_ROW_HEIGHT * 2);
    1260     searchPane.setPreferredSize(new Dimension(900, 50));
    1261
    1262     //default detail panel to contain detail panel - if searchResultsVisible is true, when a search if triggered, update detail pane to contain search results
    1263     detailPane = new JScrollPane(detail);
    1264     detailPane.setPreferredSize(new Dimension(900, 50));
    1265
    1266     detailPanel.add(detailPane, BorderLayout.CENTER);
    1267
    1268     JPanel eventsAndStatusPanel = new JPanel(new BorderLayout());
    1269
    1270     eventsPane = new JScrollPane(table);
    1271     eventsPane.getVerticalScrollBar().setUnitIncrement(ChainsawConstants.DEFAULT_ROW_HEIGHT * 2);
    1272
    1273     eventsAndStatusPanel.add(eventsPane, BorderLayout.CENTER);
    1274
    1275     Integer scrollBarWidth = (Integer) UIManager.get("ScrollBar.width");
    1276
    1277     JPanel rightPanel = new JPanel();
    1278     rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
    1279
    1280     JPanel rightThumbNailPanel = new JPanel();
    1281     rightThumbNailPanel.setLayout(new BoxLayout(rightThumbNailPanel, BoxLayout.Y_AXIS));
    1282     rightThumbNailPanel.add(Box.createVerticalStrut(scrollBarWidth.intValue()));
    1283     colorizedEventAndSearchMatchThumbnail = new ColorizedEventAndSearchMatchThumbnail();
    1284     rightThumbNailPanel.add(colorizedEventAndSearchMatchThumbnail);
    1285     rightThumbNailPanel.add(Box.createVerticalStrut(scrollBarWidth.intValue()));
    1286     rightPanel.add(rightThumbNailPanel);
    1287     //set thumbnail width to be a bit narrower than scrollbar width
    1288     if (scrollBarWidth != null) {
    1289         rightThumbNailPanel.setPreferredSize(new Dimension(scrollBarWidth.intValue() -4, -1));
    1290     }
    1291     eventsAndStatusPanel.add(rightPanel, BorderLayout.EAST);
    1292
    1293     JPanel leftPanel = new JPanel();
    1294     leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS));
    1295
    1296     JPanel leftThumbNailPanel = new JPanel();
    1297     leftThumbNailPanel.setLayout(new BoxLayout(leftThumbNailPanel, BoxLayout.Y_AXIS));
    1298     leftThumbNailPanel.add(Box.createVerticalStrut(scrollBarWidth.intValue()));
    1299     eventTimeDeltaMatchThumbnail = new EventTimeDeltaMatchThumbnail();
    1300     leftThumbNailPanel.add(eventTimeDeltaMatchThumbnail);
    1301     leftThumbNailPanel.add(Box.createVerticalStrut(scrollBarWidth.intValue()));
    1302     leftPanel.add(leftThumbNailPanel);
    1303
    1304     //set thumbnail width to be a bit narrower than scrollbar width
    1305     if (scrollBarWidth != null) {
    1306         leftThumbNailPanel.setPreferredSize(new Dimension(scrollBarWidth.intValue() -4, -1));
    1307     }
    1308     eventsAndStatusPanel.add(leftPanel, BorderLayout.WEST);
    1309
    1310     final JPanel statusLabelPanel = new JPanel();
    1311     statusLabelPanel.setLayout(new BorderLayout());
    1312
    1313     statusLabelPanel.add(upperPanel, BorderLayout.CENTER);
    1314     eventsAndStatusPanel.add(statusLabelPanel, BorderLayout.NORTH);
    1315
    1316     /*
    1317      * Detail panel layout editor
    1318      */
    1319     detailToolbar = new JToolBar(SwingConstants.HORIZONTAL);
    1320     detailToolbar.setFloatable(false);
    1321
    1322     final LayoutEditorPane layoutEditorPane = new LayoutEditorPane();
    1323     final JDialog layoutEditorDialog =
    1324       new JDialog((JFrame) null, "Pattern Editor");
    1325     layoutEditorDialog.getContentPane().add(layoutEditorPane);
    1326     layoutEditorDialog.setSize(640, 480);
    1327
    1328     layoutEditorPane.addCancelActionListener(
    1329       new ActionListener() {
    1330         public void actionPerformed(ActionEvent e) {
    1331           layoutEditorDialog.setVisible(false);
    1332         }
    1333       });
    1334
    1335     layoutEditorPane.addOkActionListener(
    1336       new ActionListener() {
    1337         public void actionPerformed(ActionEvent e) {
    1338           setDetailPaneConversionPattern(
    1339             layoutEditorPane.getConversionPattern());
    1340           layoutEditorDialog.setVisible(false);
    1341         }
    1342       });
    1343
    1344     Action copyToRefineFocusAction = new AbstractAction("Set 'refine focus' field") {
    1345         public void actionPerformed(ActionEvent e) {
    1346             String selectedText = detail.getSelectedText();
    1347             if (selectedText == null || selectedText.equals("")) {
    1348                 //no-op empty searches
    1349                 return;
    1350             }
    1351             filterText.setText("msg ~= '" + selectedText + "'");
    1352         }
    1353     };
    1354
    1355     Action copyToSearchAction = new AbstractAction("Find next") {
    1356         public void actionPerformed(ActionEvent e) {
    1357             String selectedText = detail.getSelectedText();
    1358             if (selectedText == null || selectedText.equals("")) {
    1359                 //no-op empty searches
    1360                 return;
    1361             }
    1362             findCombo.setSelectedItem("msg ~= '" + selectedText + "'");
    1363             findNext();
    1364         }
    1365     };
    1366
    1367     Action editDetailAction =
    1368       new AbstractAction(
    1369         "Edit...", new ImageIcon(ChainsawIcons.ICON_EDIT_RECEIVER)) {
    1370         public void actionPerformed(ActionEvent e) {
    1371           layoutEditorPane.setConversionPattern(
    1372             getDetailPaneConversionPattern());
    1373
    1374           Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
    1375           Point p =
    1376             new Point(
    1377               ((int) ((size.getWidth() / 2)
    1378               - (layoutEditorDialog.getSize().getWidth() / 2))),
    1379               ((int) ((size.getHeight() / 2)
    1380               - (layoutEditorDialog.getSize().getHeight() / 2))));
    1381           layoutEditorDialog.setLocation(p);
    1382
    1383           layoutEditorDialog.setVisible(true);
    1384         }
    1385       };
    1386
    1387     editDetailAction.putValue(
    1388       Action.SHORT_DESCRIPTION,
    1389       "opens a Dialog window to Edit the Pattern Layout text");
    1390
    1391     final SmallButton editDetailButton = new SmallButton(editDetailAction);
    1392     editDetailButton.setText(null);
    1393     detailToolbar.add(Box.createHorizontalGlue());
    1394     detailToolbar.add(editDetailButton);
    1395     detailToolbar.addSeparator();
    1396     detailToolbar.add(Box.createHorizontalStrut(5));
    1397
    1398     Action closeDetailAction =
    1399       new AbstractAction(null, LineIconFactory.createCloseIcon()) {
    1400         public void actionPerformed(ActionEvent arg0) {
    1401           preferenceModel.setDetailPaneVisible(false);
    1402         }
    1403       };
    1404
    1405     closeDetailAction.putValue(
    1406       Action.SHORT_DESCRIPTION, "Hides the Detail Panel");
    1407
    1408     SmallButton closeDetailButton = new SmallButton(closeDetailAction);
    1409     detailToolbar.add(closeDetailButton);
    1410
    1411     detailPanel.add(detailToolbar, BorderLayout.NORTH);
    1412
    1413     lowerPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT, eventsAndStatusPanel, detailPanel);
    1414
    1415     dividerSize = lowerPanel.getDividerSize();
    1416     lowerPanel.setDividerLocation(-1);
    1417
    1418     lowerPanel.setResizeWeight(1.0);
    1419     lowerPanel.setBorder(null);
    1420     lowerPanel.setContinuousLayout(true);
    1421
    1422     JPopupMenu editDetailPopupMenu = new JPopupMenu();
    1423
    1424     editDetailPopupMenu.add(copyToRefineFocusAction);
    1425     editDetailPopupMenu.add(copyToSearchAction);
    1426     editDetailPopupMenu.addSeparator();
    1427
    1428     editDetailPopupMenu.add(editDetailAction);
    1429     editDetailPopupMenu.addSeparator();
    1430
    1431     final ButtonGroup layoutGroup = new ButtonGroup();
    1432
    1433     JRadioButtonMenuItem defaultLayoutRadio =
    1434       new JRadioButtonMenuItem(
    1435         new AbstractAction("Set to Default Layout") {
    1436           public void actionPerformed(ActionEvent e) {
    1437             setDetailPaneConversionPattern(
    1438               DefaultLayoutFactory.getDefaultPatternLayout());
    1439           }
    1440         });
    1441
    1442         JRadioButtonMenuItem fullLayoutRadio =
    1443           new JRadioButtonMenuItem(
    1444             new AbstractAction("Set to Full Layout") {
    1445               public void actionPerformed(ActionEvent e) {
    1446                 setDetailPaneConversionPattern(
    1447                   DefaultLayoutFactory.getFullPatternLayout());
    1448               }
    1449             });
    1450
    1451     editDetailPopupMenu.add(defaultLayoutRadio);
    1452     editDetailPopupMenu.add(fullLayoutRadio);
    1453
    1454     layoutGroup.add(defaultLayoutRadio);
    1455     layoutGroup.add(fullLayoutRadio);
    1456     defaultLayoutRadio.setSelected(true);
    1457
    1458     JRadioButtonMenuItem tccLayoutRadio =
    1459       new JRadioButtonMenuItem(
    1460         new AbstractAction("Set to TCCLayout") {
    1461           public void actionPerformed(ActionEvent e) {
    1462             setDetailPaneConversionPattern(
    1463               PatternLayout.TTCC_CONVERSION_PATTERN);
    1464           }
    1465         });
    1466     editDetailPopupMenu.add(tccLayoutRadio);
    1467     layoutGroup.add(tccLayoutRadio);
    1468
    1469     PopupListener editDetailPopupListener =
    1470       new PopupListener(editDetailPopupMenu);
    1471     detail.addMouseListener(editDetailPopupListener);
    1472
    1473     /*
    1474      * Logger tree splitpane definition
    1475      */
    1476     nameTreeAndMainPanelSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, logTreePanel, lowerPanel);
    1477     nameTreeAndMainPanelSplit.setDividerLocation(-1);
    1478
    1479     add(nameTreeAndMainPanelSplit, BorderLayout.CENTER);
    1480
    1481     if (isLogTreeVisible()) {
    1482         showLogTreePanel();
    1483     } else {
    1484         hideLogTreePanel();
    1485     }
    1486
    1487     /*
    1488      * Other menu items
    1489      */
    1490     class BestFit extends JMenuItem {
    1491       public BestFit() {
    1492         super("Best fit column");
    1493     addActionListener(
    1494       new ActionListener() {
    1495         public void actionPerformed(ActionEvent evt) {
    1496           if (currentPoint != null) {
    1497             int column = currentTable.columnAtPoint(currentPoint);
    1498             int maxWidth = getMaxColumnWidth(column);
    1499             currentTable.getColumnModel().getColumn(column).setPreferredWidth(
    1500               maxWidth);
    1501           }
    1502         }
    1503       });
    1504       }
    1505     }
    1506
    1507     class ColorPanel extends JMenuItem {
    1508       public ColorPanel() {
    1509         super("Color settings...");
    1510         setIcon(ChainsawIcons.ICON_PREFERENCES);
    1511   addActionListener(
    1512       new ActionListener() {
    1513         public void actionPerformed(ActionEvent evt) {
    1514           showColorPreferences();
    1515         }
    1516       });
    1517       }
    1518     }
    1519
    1520     class LogPanelPreferences extends JMenuItem {
    1521       public LogPanelPreferences() {
    1522         super("Tab Preferences...");
    1523         setIcon(ChainsawIcons.ICON_PREFERENCES);
    1524     addActionListener(
    1525       new ActionListener() {
    1526         public void actionPerformed(ActionEvent evt) {
    1527           showPreferences();
    1528         }
    1529       });
    1530     }
    1531   }
    1532
    1533     class FocusOn extends JMenuItem {
    1534       public FocusOn() {
    1535         super("Set 'refine focus' field to value under pointer");
    1536     addActionListener(
    1537       new ActionListener() {
    1538         public void actionPerformed(ActionEvent evt) {
    1539           if (currentPoint != null) {
    1540             String operator = "==";
    1541             int column = currentTable.columnAtPoint(currentPoint);
    1542             int row = currentTable.rowAtPoint(currentPoint);
    1543             String colName = currentTable.getColumnName(column).toUpperCase();
    1544             String value = getValueOf(row, column);
    1545
    1546             if (columnNameKeywordMap.containsKey(colName)) {
    1547               filterText.setText(
    1548                 columnNameKeywordMap.get(colName).toString() + " " + operator
    1549                 + " '" + value + "'");
    1550             }
    1551           }
    1552         }
    1553       });
    1554       }
    1555     }
    1556
    1557     class DefineAddCustomFilter extends JMenuItem {
    1558       public DefineAddCustomFilter() {
    1559         super("Add value under pointer to 'refine focus' field");
    1560   addActionListener(
    1561       new ActionListener() {
    1562         public void actionPerformed(ActionEvent evt) {
    1563           if (currentPoint != null) {
    1564             String operator = "==";
    1565             int column = currentTable.columnAtPoint(currentPoint);
    1566             int row = currentTable.rowAtPoint(currentPoint);
    1567             String value = getValueOf(row, column);
    1568             String colName = currentTable.getColumnName(column).toUpperCase();
    1569
    1570             if (columnNameKeywordMap.containsKey(colName)) {
    1571               filterText.setText(
    1572                 filterText.getText() + " && "
    1573                 + columnNameKeywordMap.get(colName).toString() + " "
    1574                 + operator + " '" + value + "'");
    1575             }
    1576
    1577           }
    1578         }
    1579       });
    1580       }
    1581     }
    1582
    1583         class DefineAddCustomFind extends JMenuItem {
    1584       public DefineAddCustomFind() {
    1585         super("Add value under pointer to 'find' field");
    1586   addActionListener(
    1587       new ActionListener() {
    1588         public void actionPerformed(ActionEvent evt) {
    1589           if (currentPoint != null) {
    1590             String operator = "==";
    1591             int column = currentTable.columnAtPoint(currentPoint);
    1592             int row = currentTable.rowAtPoint(currentPoint);
    1593             String value = getValueOf(row, column);
    1594             String colName = currentTable.getColumnName(column).toUpperCase();
    1595
    1596             if (columnNameKeywordMap.containsKey(colName)) {
    1597               findCombo.setSelectedItem(
    1598                 findText.getText() + " && "
    1599                 + columnNameKeywordMap.get(colName).toString() + " "
    1600                 + operator + " '" + value + "'");
    1601               findNext();
    1602             }
    1603           }
    1604         }
    1605       });
    1606       }
    1607     }
    1608
    1609     class BuildColorRule extends JMenuItem {
    1610       public BuildColorRule() {
    1611         super("Define color rule for value under pointer");
    1612       addActionListener(
    1613       new ActionListener() {
    1614         public void actionPerformed(ActionEvent evt) {
    1615           if (currentPoint != null) {
    1616             String operator = "==";
    1617             int column = currentTable.columnAtPoint(currentPoint);
    1618             int row = currentTable.rowAtPoint(currentPoint);
    1619             String colName = currentTable.getColumnName(column).toUpperCase();
    1620             String value = getValueOf(row, column);
    1621
    1622             if (columnNameKeywordMap.containsKey(colName)) {
    1623                 Color c = JColorChooser.showDialog(getRootPane(), "Choose a color", Color.red);
    1624                 if (c != null) {
    1625                     String expression = columnNameKeywordMap.get(colName).toString() + " " + operator + " '" + value + "'";
    1626                     colorizer.addRule(ChainsawConstants.DEFAULT_COLOR_RULE_NAME, new ColorRule(expression,
    1627                             ExpressionRule.getRule(expression), c, ChainsawConstants.COLOR_DEFAULT_FOREGROUND));
    1628                 }
    1629             }
    1630           }
    1631         }
    1632       });
    1633       }
    1634     }
    1635
    1636     final JPopupMenu mainPopup = new JPopupMenu();
    1637     final JPopupMenu searchPopup = new JPopupMenu();
    1638
    1639     class ClearFocus extends AbstractAction {
    1640       public ClearFocus() {
    1641         super("Clear 'refine focus' field");
    1642       }
    1643         public void actionPerformed(ActionEvent e) {
    1644           filterText.setText(null);
    1645           tableRuleMediator.setFilterRule(null);
    1646           searchRuleMediator.setFilterRule(null);
    1647         }
    1648       }
    1649
    1650     class CopySelection extends AbstractAction {
    1651       public CopySelection() {
    1652         super("Copy selection to clipboard");
    1653       }
    1654
    1655         public void actionPerformed(ActionEvent e) {
    1656           if (currentTable == null) {
    1657             return;
    1658           }
    1659           int start = currentTable.getSelectionModel().getMinSelectionIndex();
    1660           int end = currentTable.getSelectionModel().getMaxSelectionIndex();
    1661           StringBuffer result = new StringBuffer();
    1662           for (int row=start;row<end+1;row++) {
    1663             for (int column=0;column<currentTable.getColumnCount();column++) {
    1664               result.append(getValueOf(row, column));
    1665               if (column != (currentTable.getColumnCount() - 1)) {
    1666                 result.append(" - ");
    1667               }
    1668             }
    1669             result.append(System.getProperty("line.separator"));
    1670           }
    1671           StringSelection selection = new StringSelection(result.toString());
    1672           Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    1673           clipboard.setContents(selection, null);
    1674       }
    1675     }
    1676
    1677     class CopyField extends AbstractAction {
    1678       public CopyField() {
    1679         super("Copy value under pointer to clipboard");
    1680       }
    1681
    1682         public void actionPerformed(ActionEvent e) {
    1683           if (currentPoint != null && currentTable != null) {
    1684             int column = currentTable.columnAtPoint(currentPoint);
    1685             int row = currentTable.rowAtPoint(currentPoint);
    1686             String value = getValueOf(row, column);
    1687             StringSelection selection = new StringSelection(value);
    1688             Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    1689             clipboard.setContents(selection, null);
    1690         }
    1691       }
    1692     }
    1693     final JMenuItem menuItemToggleDock = new JMenuItem("Undock/dock");
    1694
    1695     dockingAction =
    1696       new AbstractAction("Undock") {
    1697           public void actionPerformed(ActionEvent evt) {
    1698             if (isDocked()) {
    1699               undock();
    1700             } else {
    1701               dock();
    1702             }
    1703           }
    1704         };
    1705     dockingAction.putValue(
    1706       Action.SMALL_ICON, new ImageIcon(ChainsawIcons.UNDOCK));
    1707     menuItemToggleDock.setAction(dockingAction);
    1708
    1709     /*
    1710      * Popup definition
    1711      */
    1712     mainPopup.add(new FocusOn());
    1713     searchPopup.add(new FocusOn());
    1714     mainPopup.add(new DefineAddCustomFilter());
    1715     searchPopup.add(new DefineAddCustomFilter());
    1716     mainPopup.add(new ClearFocus());
    1717     searchPopup.add(new ClearFocus());
    1718
    1719     mainPopup.add(new JSeparator());
    1720     searchPopup.add(new JSeparator());
    1721
    1722     class Search extends JMenuItem {
    1723       public Search() {
    1724         super("Find value under pointer");
    1725
    1726     addActionListener(
    1727       new ActionListener() {
    1728         public void actionPerformed(ActionEvent evt) {
    1729           if (currentPoint != null) {
    1730             String operator = "==";
    1731             int column = currentTable.columnAtPoint(currentPoint);
    1732             int row = currentTable.rowAtPoint(currentPoint);
    1733             String colName = currentTable.getColumnName(column).toUpperCase();
    1734             String value = getValueOf(row, column);
    1735             if (columnNameKeywordMap.containsKey(colName)) {
    1736               findCombo.setSelectedItem(
    1737                 columnNameKeywordMap.get(colName).toString() + " " + operator
    1738                 + " '" + value + "'");
    1739               findNext();
    1740             }
    1741           }
    1742         }
    1743       });
    1744       }
    1745     }
    1746
    1747      class ClearSearch extends AbstractAction {
    1748        public ClearSearch() {
    1749          super("Clear find field");
    1750        }
    1751           public void actionPerformed(ActionEvent e) {
    1752             findCombo.setSelectedItem(null);
    1753             updateFindRule(null);
    1754           }
    1755         }
    1756
    1757     mainPopup.add(new Search());
    1758     searchPopup.add(new Search());
    1759     mainPopup.add(new DefineAddCustomFind());
    1760     searchPopup.add(new DefineAddCustomFind());
    1761     mainPopup.add(new ClearSearch());
    1762     searchPopup.add(new ClearSearch());
    1763
    1764     mainPopup.add(new JSeparator());
    1765     searchPopup.add(new JSeparator());
    1766
    1767     class DisplayNormalTimes extends JMenuItem {
    1768       public DisplayNormalTimes() {
    1769         super("Hide relative times");
    1770   addActionListener(
    1771       new ActionListener() {
    1772         public void actionPerformed(ActionEvent e) {
    1773           if (currentPoint != null) {
    1774             ((TableColorizingRenderer)currentTable.getDefaultRenderer(Object.class)).setUseNormalTimes();
    1775             ((ChainsawCyclicBufferTableModel)currentTable.getModel()).reFilter();
    1776             setEnabled(true);
    1777           }
    1778         }
    1779     });
    1780       }
    1781     }
    1782
    1783     class DisplayRelativeTimesToRowUnderCursor extends JMenuItem {
    1784     public DisplayRelativeTimesToRowUnderCursor() {
    1785       super("Show times relative to this event");
    1786       addActionListener(
    1787       new ActionListener() {
    1788         public void actionPerformed(ActionEvent e) {
    1789             if (currentPoint != null) {
    1790               int row = currentTable.rowAtPoint(currentPoint);
    1791               ChainsawCyclicBufferTableModel cyclicBufferTableModel = (ChainsawCyclicBufferTableModel) currentTable.getModel();
    1792               LoggingEventWrapper loggingEventWrapper = cyclicBufferTableModel.getRow(row);
    1793               if (loggingEventWrapper != null)
    1794               {
    1795                   ((TableColorizingRenderer)currentTable.getDefaultRenderer(Object.class)).setUseRelativeTimes(loggingEventWrapper.getLoggingEvent().getTimeStamp());
    1796                   cyclicBufferTableModel.reFilter();
    1797               }
    1798               setEnabled(true);
    1799             }
    1800         }
    1801       });
    1802     }
    1803     }
    1804
    1805     class DisplayRelativeTimesToPreviousRow extends JMenuItem {
    1806       public DisplayRelativeTimesToPreviousRow() {
    1807         super("Show times relative to previous rows");
    1808       addActionListener(
    1809         new ActionListener() {
    1810           public void actionPerformed(ActionEvent e) {
    1811               if (currentPoint != null) {
    1812                 ((TableColorizingRenderer)currentTable.getDefaultRenderer(Object.class)).setUseRelativeTimesToPreviousRow();
    1813                 ((ChainsawCyclicBufferTableModel)currentTable.getModel()).reFilter();
    1814                 setEnabled(true);
    1815               }
    1816           }
    1817         });
    1818       }
    1819     }
    1820
    1821     mainPopup.add(new DisplayRelativeTimesToRowUnderCursor());
    1822     searchPopup.add(new DisplayRelativeTimesToRowUnderCursor());
    1823     mainPopup.add(new DisplayRelativeTimesToPreviousRow());
    1824     searchPopup.add(new DisplayRelativeTimesToPreviousRow());
    1825     mainPopup.add(new DisplayNormalTimes());
    1826     searchPopup.add(new DisplayNormalTimes());
    1827     mainPopup.add(new JSeparator());
    1828     searchPopup.add(new JSeparator());
    1829
    1830     mainPopup.add(new BuildColorRule());
    1831     searchPopup.add(new BuildColorRule());
    1832     mainPopup.add(new JSeparator());
    1833     searchPopup.add(new JSeparator());
    1834     mainPopup.add(new CopyField());
    1835     mainPopup.add(new CopySelection());
    1836     searchPopup.add(new CopyField());
    1837     searchPopup.add(new CopySelection());
    1838     mainPopup.add(new JSeparator());
    1839     searchPopup.add(new JSeparator());
    1840
    1841     mainPopup.add(menuItemToggleDetails);
    1842     mainPopup.add(menuItemLoggerTree);
    1843     mainToggleToolTips = new ToggleToolTips();
    1844     searchToggleToolTips = new ToggleToolTips();
    1845     mainPopup.add(mainToggleToolTips);
    1846     searchPopup.add(searchToggleToolTips);
    1847
    1848     mainPopup.add(new JSeparator());
    1849
    1850     mainPopup.add(menuItemToggleDock);
    1851
    1852     mainPopup.add(new BestFit());
    1853     searchPopup.add(new BestFit());
    1854
    1855     mainPopup.add(new JSeparator());
    1856
    1857     mainPopup.add(new ColorPanel());
    1858     searchPopup.add(new ColorPanel());
    1859     mainPopup.add(new LogPanelPreferences());
    1860     searchPopup.add(new LogPanelPreferences());
    1861
    1862     final PopupListener mainTablePopupListener = new PopupListener(mainPopup);
    1863     eventsPane.addMouseListener(mainTablePopupListener);
    1864     table.addMouseListener(mainTablePopupListener);
    1865
    1866     table.addMouseListener(new MouseListener(){
    1867       public void mouseClicked(MouseEvent mouseEvent) {
    1868         checkMultiSelect(mouseEvent);
    1869       }
    1870
    1871       public void mousePressed(MouseEvent mouseEvent) {
    1872         checkMultiSelect(mouseEvent);
    1873       }
    1874
    1875       public void mouseReleased(MouseEvent mouseEvent) {
    1876         checkMultiSelect(mouseEvent);
    1877       }
    1878
    1879       public void mouseEntered(MouseEvent mouseEvent) {
    1880         checkMultiSelect(mouseEvent);
    1881       }
    1882
    1883       public void mouseExited(MouseEvent mouseEvent) {
    1884         checkMultiSelect(mouseEvent);
    1885       }
    1886
    1887       private void checkMultiSelect(MouseEvent mouseEvent) {
    1888         if (mouseEvent.isAltDown()) {
    1889           table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
    1890         } else {
    1891           table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    1892         }
    1893       }
    1894     });
    1895
    1896
    1897     searchTable.addMouseListener(new MouseListener(){
    1898       public void mouseClicked(MouseEvent mouseEvent) {
    1899         checkMultiSelect(mouseEvent);
    1900       }
    1901
    1902       public void mousePressed(MouseEvent mouseEvent) {
    1903         checkMultiSelect(mouseEvent);
    1904       }
    1905
    1906       public void mouseReleased(MouseEvent mouseEvent) {
    1907         checkMultiSelect(mouseEvent);
    1908       }
    1909
    1910       public void mouseEntered(MouseEvent mouseEvent) {
    1911         checkMultiSelect(mouseEvent);
    1912       }
    1913
    1914       public void mouseExited(MouseEvent mouseEvent) {
    1915         checkMultiSelect(mouseEvent);
    1916       }
    1917
    1918       private void checkMultiSelect(MouseEvent mouseEvent) {
    1919         if (mouseEvent.isAltDown()) {
    1920           searchTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
    1921         } else {
    1922           searchTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    1923         }
    1924       }
    1925     });
    1926
    1927
    1928     final PopupListener searchTablePopupListener = new PopupListener(searchPopup);
    1929     searchPane.addMouseListener(searchTablePopupListener);
    1930     searchTable.addMouseListener(searchTablePopupListener);
    1931   }
    1932
    1933        private String getValueOf(int row, int column) {
    1934          if (currentTable == null) {
    1935            return "";
    1936          }
    1937
    1938          Object o = currentTable.getValueAt(row, column);
    1939
    1940          if (o instanceof Date) {
    1941            return TIMESTAMP_DATE_FORMAT.format((Date)o);
    1942          }
    1943
    1944          if (o instanceof String) {
    1945            return (String)o;
    1946          }
    1947
    1948          if (o instanceof Level) {
    1949            return o.toString();
    1950          }
    1951
    1952          if (o instanceof String[]) {
    1953            String value = "";
    1954           //exception - build message + throwable
    1955           String[] ti = (String[])o;
    1956             if (ti.length > 0 && (!(ti.length == 1 && ti[0].equals("")))) {
    1957               LoggingEventWrapper loggingEventWrapper = ((ChainsawCyclicBufferTableModel)(currentTable.getModel())).getRow(row);
    1958               value = loggingEventWrapper.getLoggingEvent().getMessage().toString();
    1959               for (int i=0;i<((String[])o).length;i++) {
    1960                   value = value + "\n" + ((String[]) o)[i];
    1961               }
    1962             }
    1963            return value;
    1964          }
    1965          return "";
    1966       }
    1967
    1968     private Action getFindNextAction() {
    1969     final Action action =
    1970       new AbstractAction("Find next") {
    1971         public void actionPerformed(ActionEvent e) {
    1972           findNext();
    1973         }
    1974       };
    1975
    1976     //    action.putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_F));
    1977     action.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("F3"));
    1978     action.putValue(
    1979       Action.SHORT_DESCRIPTION,
    1980       "Find the next occurrence of the rule from the current row");
    1981     action.putValue(Action.SMALL_ICON, new ImageIcon(ChainsawIcons.DOWN));
    1982
    1983     return action;
    1984   }
    1985
    1986   private Action getFindPreviousAction() {
    1987     final Action action =
    1988       new AbstractAction("Find previous") {
    1989         public void actionPerformed(ActionEvent e) {
    1990             findPrevious();
    1991         }
    1992       };
    1993
    1994     //    action.putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_F));
    1995     action.putValue(
    1996       Action.ACCELERATOR_KEY,
    1997       KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.SHIFT_MASK));
    1998     action.putValue(
    1999       Action.SHORT_DESCRIPTION,
    2000       "Find the previous occurrence of the rule from the current row");
    2001     action.putValue(Action.SMALL_ICON, new ImageIcon(ChainsawIcons.UP));
    2002
    2003     return action;
    2004   }
    2005
    2006   private  void buildCombo(final AutoFilterComboBox combo, boolean isFiltering, final AutoFilterComboBox.AutoFilterComboBoxModel otherModel) {
    2007     //add (hopefully useful) default filters
    2008     combo.addItem("LEVEL == TRACE");
    2009     combo.addItem("LEVEL >= DEBUG");
    2010     combo.addItem("LEVEL >= INFO");
    2011     combo.addItem("LEVEL >= WARN");
    2012     combo.addItem("LEVEL >= ERROR");
    2013     combo.addItem("LEVEL == FATAL");
    2014
    2015     final JTextField filterText =(JTextField) combo.getEditor().getEditorComponent();
    2016     if (isFiltering) {
    2017       filterText.getDocument().addDocumentListener(new DelayedTextDocumentListener(filterText));
    2018     }
    2019     filterText.setToolTipText("Enter an expression - right click or ctrl-space for menu - press enter to add to list");
    2020     filterText.addKeyListener(new ExpressionRuleContext(filterModel, filterText));
    2021
    2022     if (combo.getEditor().getEditorComponent() instanceof JTextField) {
    2023       combo.addActionListener(
    2024         new AbstractAction() {
    2025           public void actionPerformed(ActionEvent e) {
    2026             if (e.getActionCommand().equals("comboBoxEdited")) {
    2027               try {
    2028                 //verify the expression is valid
    2029                   Object item = combo.getSelectedItem();
    2030                   if (item != null && !item.toString().trim().equals("")) {
    2031                     ExpressionRule.getRule(item.toString());
    2032                     //add entry as first row of the combo box
    2033                     combo.insertItemAt(item, 0);
    2034                     otherModel.insertElementAt(item, 0);
    2035                   }
    2036                 //valid expression, reset background color in case we were previously an invalid expression
    2037                 filterText.setBackground(UIManager.getColor("TextField.background"));
    2038               } catch (IllegalArgumentException iae) {
    2039                   //don't add expressions that aren't valid
    2040                   //invalid expression, change background of the field
    2041                   filterText.setToolTipText(iae.getMessage());
    2042                   filterText.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
    2043               }
    2044             }
    2045           }
    2046         });
    2047     }
    2048   }
    2049
    2050   /**
    2051    * Accessor
    2052    *
    2053    * @return scrollToBottom
    2054    *
    2055    */
    2056   public boolean isScrollToBottom() {
    2057   	return preferenceModel.isScrollToBottom();
    2058   }
    2059
    2060   public void setRefineFocusText(String refineFocusText) {
    2061       final JTextField filterText =(JTextField) filterCombo.getEditor().getEditorComponent();
    2062       filterText.setText(refineFocusText);
    2063   }
    2064
    2065   public String getRefineFocusText() {
    2066       final JTextField filterText =(JTextField) filterCombo.getEditor().getEditorComponent();
    2067       return filterText.getText();
    2068   }
    2069   /**
    2070    * Mutator
    2071    *
    2072    */
    2073   public void toggleScrollToBottom() {
    2074   	preferenceModel.setScrollToBottom(!preferenceModel.isScrollToBottom());
    2075   }
    2076
    2077   private void scrollToBottom() {
    2078     //run this in an invokeLater block to ensure this action is enqueued to the end of the EDT
    2079     EventQueue.invokeLater(new Runnable()
    2080     {
    2081         public void run() {
    2082           int scrollRow = tableModel.getRowCount() - 1;
    2083             table.scrollToRow(scrollRow);
    2084         }
    2085     });
    2086   }
    2087
    2088   public void scrollToTop() {
    2089       EventQueue.invokeLater(new Runnable() {
    2090           public void run() {
    2091               if (tableModel.getRowCount() > 1) {
    2092                   table.scrollToRow(0);
    2093               }
    2094           }
    2095       });
    2096   }
    2097
    2098   /**
    2099    * Accessor
    2100    *
    2101    * @return namespace
    2102    *
    2103    * @see Profileable
    2104    */
    2105   public String getNamespace() {
    2106     return getIdentifier();
    2107   }
    2108
    2109   /**
    2110    * Accessor
    2111    *
    2112    * @return identifier
    2113    *
    2114    * @see EventBatchListener
    2115    */
    2116   public String getInterestedIdentifier() {
    2117     return getIdentifier();
    2118   }
    2119
    2120   /**
    2121    * Process events associated with the identifier.  Currently assumes it only
    2122    * receives events which share this LogPanel's identifier
    2123    *
    2124    * @param ident identifier shared by events
    2125    * @param events list of LoggingEvent objects
    2126    */
    2127   public void receiveEventBatch(String ident, final List events) {
    2128
    2129     SwingHelper.invokeOnEDT(new Runnable() {
    2130       public void run() {
    2131         /*
    2132         * if this panel is paused, we totally ignore events
    2133         */
    2134         if (isPaused()) {
    2135           return;
    2136         }
    2137         final int selectedRow = table.getSelectedRow();
    2138         final int startingRow = table.getRowCount();
    2139         final LoggingEventWrapper selectedEvent;
    2140         if (selectedRow >= 0) {
    2141           selectedEvent = tableModel.getRow(selectedRow);
    2142         } else {
    2143           selectedEvent = null;
    2144         }
    2145
    2146         final int startingSearchRow = searchTable.getRowCount();
    2147
    2148         boolean rowAdded = false;
    2149         boolean searchRowAdded = false;
    2150
    2151         int addedRowCount = 0;
    2152         int searchAddedRowCount = 0;
    2153
    2154         for (Iterator iter = events.iterator(); iter.hasNext();) {
    2155           //these are actual LoggingEvent instances
    2156           LoggingEvent event = (LoggingEvent)iter.next();
    2157           //create two separate loggingEventWrappers (main table and search table), as they have different info on display state
    2158           LoggingEventWrapper loggingEventWrapper1 = new LoggingEventWrapper(event);
    2159             //if the clearTableExpressionRule is not null, evaluate & clear the table if it matches
    2160             if (clearTableExpressionRule != null && clearTableExpressionRule.evaluate(event, null)) {
    2161                 logger.info("clear table expression matched - clearing table - matching event msg - " + event.getMessage());
    2162                 clearEvents();
    2163             }
    2164
    2165           updateOtherModels(event);
    2166           boolean isCurrentRowAdded = tableModel.isAddRow(loggingEventWrapper1);
    2167           if (isCurrentRowAdded) {
    2168               addedRowCount++;
    2169           }
    2170           rowAdded = rowAdded || isCurrentRowAdded;
    2171
    2172           //create a new loggingEventWrapper via copy constructor to ensure same IDs
    2173           LoggingEventWrapper loggingEventWrapper2 = new LoggingEventWrapper(loggingEventWrapper1);
    2174           boolean isSearchCurrentRowAdded = searchModel.isAddRow(loggingEventWrapper2);
    2175           if (isSearchCurrentRowAdded) {
    2176               searchAddedRowCount++;
    2177           }
    2178           searchRowAdded = searchRowAdded || isSearchCurrentRowAdded;
    2179         }
    2180         //fire after adding all events
    2181         if (rowAdded) {
    2182           tableModel.fireTableEvent(startingRow, startingRow + addedRowCount, addedRowCount);
    2183         }
    2184         if (searchRowAdded) {
    2185           searchModel.fireTableEvent(startingSearchRow, startingSearchRow + searchAddedRowCount, searchAddedRowCount);
    2186         }
    2187
    2188         //tell the model to notify the count listeners
    2189         tableModel.notifyCountListeners();
    2190
    2191         if (rowAdded) {
    2192           if (tableModel.isSortEnabled()) {
    2193             tableModel.sort();
    2194           }
    2195
    2196           //always update detail pane (since we may be using a cyclic buffer which is full)
    2197           detailPaneUpdater.setSelectedRow(table.getSelectedRow());
    2198         }
    2199
    2200         if (searchRowAdded) {
    2201           if (searchModel.isSortEnabled()) {
    2202             searchModel.sort();
    2203           }
    2204         }
    2205
    2206         if (!isScrollToBottom() && selectedEvent != null) {
    2207           final int newIndex = tableModel.getRowIndex(selectedEvent);
    2208           if (newIndex >= 0) {
    2209             // Don't scroll, just maintain selection...
    2210             table.setRowSelectionInterval(newIndex, newIndex);
    2211           }
    2212         }
    2213       }
    2214     });
    2215   }
    2216
    2217   /**
    2218    * Load settings from the panel preference model
    2219    *
    2220    * @param event
    2221    *
    2222    * @see LogPanelPreferenceModel
    2223    */
    2224   public void loadSettings(LoadSettingsEvent event) {
    2225
    2226     File xmlFile = null;
    2227     try {
    2228       xmlFile = new File(SettingsManager.getInstance().getSettingsDirectory(), URLEncoder.encode(identifier, "UTF-8") + ".xml");
    2229     } catch (UnsupportedEncodingException e) {
    2230       e.printStackTrace();
    2231     }
    2232
    2233     if (xmlFile.exists()) {
    2234         XStream stream = buildXStreamForLogPanelPreference();
    2235         ObjectInputStream in = null;
    2236         try {
    2237             FileReader r = new FileReader(xmlFile);
    2238             in = stream.createObjectInputStream(r);
    2239             LogPanelPreferenceModel storedPrefs = (LogPanelPreferenceModel)in.readObject();
    2240             lowerPanelDividerLocation = in.readInt();
    2241             int treeDividerLocation = in.readInt();
    2242             String conversionPattern = in.readObject().toString();
    2243             Point p = (Point)in.readObject();
    2244             Dimension d = (Dimension)in.readObject();
    2245             //this version number is checked to identify whether there is a Vector comming next
    2246             int versionNumber = 0;
    2247             try {
    2248                 versionNumber = in.readInt();
    2249             } catch (EOFException eof){
    2250             }
    2251
    2252             Vector savedVector;
    2253             //read the vector only if the version number is greater than 0. higher version numbers can be
    2254             //used in the future to save more data structures
    2255             if (versionNumber > 0) {
    2256                 savedVector = (Vector) in.readObject();
    2257                 for(int i = 0 ; i < savedVector.size() ; i++){
    2258                     Object item = savedVector.get(i);
    2259                     //insert each row at index zero (so last row in vector will be row zero)
    2260                     filterCombo.insertItemAt(item, 0);
    2261                     findCombo.insertItemAt(item, 0);
    2262                 }
    2263                 if (versionNumber > 1) {
    2264                     //update prefModel columns to include defaults
    2265                     int index = 0;
    2266                     String columnOrder = event.getSetting(TABLE_COLUMN_ORDER);
    2267                     StringTokenizer tok = new StringTokenizer(columnOrder, ",");
    2268                     while (tok.hasMoreElements()) {
    2269                       String element = tok.nextElement().toString().trim().toUpperCase();
    2270                       TableColumn column = new TableColumn(index++);
    2271                       column.setHeaderValue(element);
    2272                       preferenceModel.addColumn(column);
    2273                     }
    2274
    2275                     TableColumnModel columnModel = table.getColumnModel();
    2276                     //remove previous columns
    2277                     while (columnModel.getColumnCount() > 0) {
    2278                         columnModel.removeColumn(columnModel.getColumn(0));
    2279                     }
    2280                     //add visible column order columns
    2281                     for (Iterator iter = preferenceModel.getVisibleColumnOrder().iterator();iter.hasNext();) {
    2282                         TableColumn col = (TableColumn)iter.next();
    2283                         columnModel.addColumn(col);
    2284                     }
    2285
    2286                   TableColumnModel searchColumnModel = searchTable.getColumnModel();
    2287                   //remove previous columns
    2288                   while (searchColumnModel.getColumnCount() > 0) {
    2289                       searchColumnModel.removeColumn(searchColumnModel.getColumn(0));
    2290                   }
    2291                   //add visible column order columns
    2292                   for (Iterator iter = preferenceModel.getVisibleColumnOrder().iterator();iter.hasNext();) {
    2293                       TableColumn col = (TableColumn)iter.next();
    2294                       searchColumnModel.addColumn(col);
    2295                   }
    2296
    2297                     preferenceModel.apply(storedPrefs);
    2298                 } else {
    2299                     loadDefaultColumnSettings(event);
    2300                 }
    2301                 //ensure tablemodel cyclic flag is updated
    2302                 //may be panel configs that don't have these values
    2303                 tableModel.setCyclic(preferenceModel.isCyclic());
    2304                 searchModel.setCyclic(preferenceModel.isCyclic());
    2305                 lowerPanel.setDividerLocation(lowerPanelDividerLocation);
    2306                 nameTreeAndMainPanelSplit.setDividerLocation(treeDividerLocation);
    2307                 detailLayout.setConversionPattern(conversionPattern);
    2308                 if (p.x != 0 && p.y != 0) {
    2309                     undockedFrame.setLocation(p.x, p.y);
    2310                     undockedFrame.setSize(d);
    2311                 } else {
    2312                     undockedFrame.setLocation(0, 0);
    2313                     undockedFrame.setSize(new Dimension(1024, 768));
    2314                 }
    2315             } else {
    2316                 loadDefaultColumnSettings(event);
    2317             }
    2318         } catch (Exception e) {
    2319             e.printStackTrace();
    2320             loadDefaultColumnSettings(event);
    2321             // TODO need to log this..
    2322         } finally {
    2323             if (in != null) {
    2324                 try {
    2325                     in.close();
    2326                 } catch (IOException ioe) {}
    2327             }
    2328         }
    2329     } else {
    2330         //not setting lower panel divider location here - will do that after the UI is visible
    2331         loadDefaultColumnSettings(event);
    2332     }
    2333     //ensure tablemodel cyclic flag is updated
    2334     tableModel.setCyclic(preferenceModel.isCyclic());
    2335     searchModel.setCyclic(preferenceModel.isCyclic());
    2336     logTreePanel.ignore(preferenceModel.getHiddenLoggers());
    2337     logTreePanel.setHiddenExpression(preferenceModel.getHiddenExpression());
    2338     logTreePanel.setAlwaysDisplayExpression(preferenceModel.getAlwaysDisplayExpression());
    2339     if (preferenceModel.getClearTableExpression() != null) {
    2340         try {
    2341             clearTableExpressionRule = ExpressionRule.getRule(preferenceModel.getClearTableExpression());
    2342         } catch (Exception e) {
    2343             clearTableExpressionRule = null;
    2344         }
    2345     }
    2346
    2347     //attempt to load color settings - no need to URL encode the identifier
    2348     colorizer.loadColorSettings(identifier);
    2349   }
    2350
    2351   /**
    2352    * Save preferences to the panel preference model
    2353    *
    2354    * @param event
    2355    *
    2356    * @see LogPanelPreferenceModel
    2357    */
    2358   public void saveSettings(SaveSettingsEvent event) {
    2359     File xmlFile = null;
    2360     try {
    2361       xmlFile = new File(SettingsManager.getInstance().getSettingsDirectory(), URLEncoder.encode(identifier, "UTF-8") + ".xml");
    2362     } catch (UnsupportedEncodingException e) {
    2363       e.printStackTrace();
    2364       //unable to save..just return
    2365       return;
    2366     }
    2367
    2368     preferenceModel.setHiddenLoggers(new HashSet(logTreePanel.getHiddenSet()));
    2369     preferenceModel.setHiddenExpression(logTreePanel.getHiddenExpression());
    2370     preferenceModel.setAlwaysDisplayExpression(logTreePanel.getAlwaysDisplayExpression());
    2371     List visibleOrder = new ArrayList();
    2372     Enumeration cols = table.getColumnModel().getColumns();
    2373     while (cols.hasMoreElements()) {
    2374     	TableColumn c = (TableColumn)cols.nextElement();
    2375     	visibleOrder.add(c);
    2376     }
    2377     preferenceModel.setVisibleColumnOrder(visibleOrder);
    2378     //search table will use same columns as main table
    2379
    2380     XStream stream = buildXStreamForLogPanelPreference();
    2381     ObjectOutputStream s = null;
    2382     try {
    2383     	FileWriter w = new FileWriter(xmlFile);
    2384     	s = stream.createObjectOutputStream(w);
    2385     	s.writeObject(preferenceModel);
    2386       if (isDetailPanelVisible) {
    2387         //use current size
    2388         s.writeInt(lowerPanel.getDividerLocation());
    2389       } else {
    2390         //use size when last hidden
    2391         s.writeInt(lowerPanelDividerLocation);
    2392       }
    2393     	s.writeInt(nameTreeAndMainPanelSplit.getDividerLocation());
    2394     	s.writeObject(detailLayout.getConversionPattern());
    2395     	s.writeObject(undockedFrame.getLocation());
    2396     	s.writeObject(undockedFrame.getSize());
    2397         //this is a version number written to the file to identify that there is a Vector serialized after this
    2398         s.writeInt(LOG_PANEL_SERIALIZATION_VERSION_NUMBER);
    2399         //don't write filterexpressionvector, write the combobox's model's backing vector
    2400         Vector combinedVector = new Vector();
    2401         combinedVector.addAll(filterCombo.getModelData());
    2402         combinedVector.addAll(findCombo.getModelData());
    2403         //duplicates will be removed when loaded..
    2404         s.writeObject(combinedVector);
    2405     } catch (Exception ex) {
    2406         ex.printStackTrace();
    2407         // TODO need to log this..
    2408     } finally {
    2409     	if (s != null) {
    2410     		try {
    2411     			s.close();
    2412     		} catch (IOException ioe) {}
    2413     	}
    2414     }
    2415
    2416     //no need to URL encode the identifier
    2417     colorizer.saveColorSettings(identifier);
    2418   }
    2419
    2420     private XStream buildXStreamForLogPanelPreference() {
    2421         XStream stream = new XStream(new DomDriver());
    2422         stream.registerConverter(new TableColumnConverter());
    2423         return stream;
    2424     }
    2425
    2426   /**
    2427      * Display the panel preferences frame
    2428      */
    2429   void showPreferences() {
    2430       //don't pack this frame
    2431       centerAndSetVisible(logPanelPreferencesFrame);
    2432   }
    2433
    2434   public static void centerAndSetVisible(Window window) {
    2435     Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
    2436     window.setLocation(new Point((screenDimension.width / 2) - (window.getSize().width / 2),
    2437       (screenDimension.height / 2) - (window.getSize().height / 2)));
    2438     window.setVisible(true);
    2439   }
    2440
    2441   /**
    2442    * Display the color rule frame
    2443    */
    2444   void showColorPreferences() {
    2445     colorPanel.loadLogPanelColorizers();
    2446     colorFrame.pack();
    2447     centerAndSetVisible(colorFrame);
    2448   }
    2449
    2450   /**
    2451    * Toggle panel preference for detail visibility on or off
    2452    */
    2453   void toggleDetailVisible() {
    2454     preferenceModel.setDetailPaneVisible(
    2455       !preferenceModel.isDetailPaneVisible());
    2456   }
    2457
    2458   /**
    2459    * Accessor
    2460    *
    2461    * @return detail visibility flag
    2462    */
    2463   boolean isDetailVisible() {
    2464     return preferenceModel.isDetailPaneVisible();
    2465   }
    2466
    2467   boolean isSearchResultsVisible() {
    2468     return preferenceModel.isSearchResultsVisible();
    2469   }
    2470
    2471   /**
    2472    * Toggle panel preference for logger tree visibility on or off
    2473    */
    2474   void toggleLogTreeVisible() {
    2475     preferenceModel.setLogTreePanelVisible(
    2476       !preferenceModel.isLogTreePanelVisible());
    2477   }
    2478
    2479   /**
    2480    * Accessor
    2481    *
    2482    * @return logger tree visibility flag
    2483    */
    2484   boolean isLogTreeVisible() {
    2485     return preferenceModel.isLogTreePanelVisible();
    2486   }
    2487
    2488   /**
    2489    * Return all events
    2490    *
    2491    * @return list of LoggingEvents
    2492    */
    2493   List getEvents() {
    2494     return tableModel.getAllEvents();
    2495   }
    2496
    2497   /**
    2498    * Return the events that are visible with the current filter applied
    2499    *
    2500    * @return list of LoggingEvents
    2501    */
    2502   List getFilteredEvents() {
    2503   	return tableModel.getFilteredEvents();
    2504   }
    2505
    2506   List getMatchingEvents(Rule rule) {
    2507     return tableModel.getMatchingEvents(rule);
    2508   }
    2509
    2510   /**
    2511    * Remove all events
    2512    */
    2513   void clearEvents() {
    2514     clearModel();
    2515   }
    2516
    2517   /**
    2518    * Accessor
    2519    *
    2520    * @return identifier
    2521    */
    2522   String getIdentifier() {
    2523     return identifier;
    2524   }
    2525
    2526   /**
    2527    * Undocks this DockablePanel by removing the panel from the LogUI window
    2528    * and placing it inside it's own JFrame.
    2529    */
    2530   void undock() {
    2531   	final int row = table.getSelectedRow();
    2532     setDocked(false);
    2533     externalPanel.removeAll();
    2534
    2535     externalPanel.add(undockedToolbar, BorderLayout.NORTH);
    2536     externalPanel.add(nameTreeAndMainPanelSplit, BorderLayout.CENTER);
    2537     externalPanel.setDocked(false);
    2538     undockedFrame.pack();
    2539
    2540     undockedFrame.setVisible(true);
    2541     dockingAction.putValue(Action.NAME, "Dock");
    2542     dockingAction.putValue(Action.SMALL_ICON, ChainsawIcons.ICON_DOCK);
    2543     if (row > -1) {
    2544         EventQueue.invokeLater(new Runnable() {
    2545             public void run() {
    2546                 table.scrollToRow(row);
    2547             }
    2548         });
    2549     }
    2550   }
    2551
    2552   /**
    2553    * Add an eventCountListener
    2554    *
    2555    * @param l
    2556    */
    2557   void addEventCountListener(EventCountListener l) {
    2558     tableModel.addEventCountListener(l);
    2559   }
    2560
    2561   /**
    2562    * Accessor
    2563    *
    2564    * @return paused flag
    2565    */
    2566   boolean isPaused() {
    2567     return paused;
    2568   }
    2569
    2570   /**
    2571    * Modifies the Paused property and notifies the listeners
    2572    *
    2573    * @param paused
    2574    */
    2575   void setPaused(boolean paused) {
    2576     boolean oldValue = this.paused;
    2577     this.paused = paused;
    2578     firePropertyChange("paused", oldValue, paused);
    2579   }
    2580
    2581   /**
    2582    * Change the selected event on the log panel.  Will cause scrollToBottom to be turned off.
    2583    *
    2584    * @param eventNumber
    2585    * @return row number or -1 if row with log4jid property with that number was not found
    2586    */
    2587   int setSelectedEvent(int eventNumber) {
    2588       int row = tableModel.locate(ExpressionRule.getRule("prop.log4jid == " + eventNumber), 0, true);
    2589       if (row > -1) {
    2590         preferenceModel.setScrollToBottom(false);
    2591
    2592         table.scrollToRow(row);
    2593       }
    2594       return row;
    2595   }
    2596
    2597   /**
    2598    * Add a preference propertyChangeListener
    2599    *
    2600    * @param listener
    2601    */
    2602   void addPreferencePropertyChangeListener(PropertyChangeListener listener) {
    2603     preferenceModel.addPropertyChangeListener(listener);
    2604   }
    2605
    2606   /**
    2607    * Toggle the LoggingEvent container from either managing a cyclic buffer of
    2608    * events or an ArrayList of events
    2609    */
    2610   void toggleCyclic() {
    2611     boolean toggledCyclic = !preferenceModel.isCyclic();
    2612
    2613     preferenceModel.setCyclic(toggledCyclic);
    2614     tableModel.setCyclic(toggledCyclic);
    2615     searchModel.setCyclic(toggledCyclic);
    2616   }
    2617
    2618   /**
    2619    * Accessor
    2620    *
    2621    * @return flag answering if LoggingEvent container is a cyclic buffer
    2622    */
    2623   boolean isCyclic() {
    2624     return preferenceModel.isCyclic();
    2625   }
    2626
    2627   public void updateFindRule(String ruleText) {
    2628     if ((ruleText == null) || (ruleText.trim().equals(""))) {
    2629       findRule = null;
    2630       tableModel.updateEventsWithFindRule(null);
    2631       colorizer.setFindRule(null);
    2632       tableRuleMediator.setFindRule(null);
    2633       searchRuleMediator.setFindRule(null);
    2634       //reset background color in case we were previously an invalid expression
    2635       findCombo.setBackground(UIManager.getColor("TextField.background"));
    2636       findCombo.setToolTipText(
    2637         "Enter an expression - right click or ctrl-space for menu - press enter to add to list");
    2638       currentSearchMatchCount = 0;
    2639       currentFindRuleText = null;
    2640       statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
    2641       //if the preference to show search results is enabled, the find rule is now null - hide search results
    2642       if (isSearchResultsVisible()) {
    2643         hideSearchResults();
    2644       }
    2645     } else {
    2646       //only turn off scrolltobottom when finding something (find not empty)
    2647       preferenceModel.setScrollToBottom(false);
    2648       if(ruleText.equals(currentFindRuleText)) {
    2649           //don't update events if rule hasn't changed (we're finding next/previous)
    2650           return;
    2651       }
    2652       currentFindRuleText = ruleText;
    2653       try {
    2654         final JTextField findText =(JTextField) findCombo.getEditor().getEditorComponent();
    2655         findText.setToolTipText(
    2656           "Enter an expression - right click or ctrl-space for menu - press enter to add to list");
    2657         findRule = ExpressionRule.getRule(ruleText);
    2658         currentSearchMatchCount = tableModel.updateEventsWithFindRule(findRule);
    2659         searchModel.updateEventsWithFindRule(findRule);
    2660         colorizer.setFindRule(findRule);
    2661         tableRuleMediator.setFindRule(findRule);
    2662         searchRuleMediator.setFindRule(findRule);
    2663         //valid expression, reset background color in case we were previously an invalid expression
    2664         findText.setBackground(UIManager.getColor("TextField.background"));
    2665         statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
    2666         if (isSearchResultsVisible()) {
    2667           showSearchResults();
    2668         }
    2669       } catch (IllegalArgumentException re) {
    2670         findRule = null;
    2671         final JTextField findText =(JTextField) findCombo.getEditor().getEditorComponent();
    2672         findText.setToolTipText(re.getMessage());
    2673         findText.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
    2674         colorizer.setFindRule(null);
    2675         tableRuleMediator.setFindRule(null);
    2676         searchRuleMediator.setFindRule(null);
    2677         tableModel.updateEventsWithFindRule(null);
    2678         searchModel.updateEventsWithFindRule(null);
    2679         currentSearchMatchCount = 0;
    2680         statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
    2681         //if the preference to show search results is enabled, the find rule is now null - hide search results
    2682         if (isSearchResultsVisible()) {
    2683           hideSearchResults();
    2684         }
    2685       }
    2686     }
    2687   }
    2688
    2689   private void hideSearchResults() {
    2690     if (searchResultsDisplayed) {
    2691       detailPanel.removeAll();
    2692       JPanel leftSpacePanel = new JPanel();
    2693       Integer scrollBarWidth = (Integer) UIManager.get("ScrollBar.width");
    2694       leftSpacePanel.setPreferredSize(new Dimension(scrollBarWidth.intValue() -4, -1));
    2695
    2696       JPanel rightSpacePanel = new JPanel();
    2697       rightSpacePanel.setPreferredSize(new Dimension(scrollBarWidth.intValue() -4, -1));
    2698
    2699       detailPanel.add(detailToolbar, BorderLayout.NORTH);
    2700       detailPanel.add(detailPane, BorderLayout.CENTER);
    2701
    2702       detailPanel.add(leftSpacePanel, BorderLayout.WEST);
    2703       detailPanel.add(rightSpacePanel, BorderLayout.EAST);
    2704
    2705       detailPanel.revalidate();
    2706       detailPanel.repaint();
    2707       //if the detail visible pref is not enabled, hide the detail pane
    2708       searchResultsDisplayed = false;
    2709       //hide if pref is not enabled
    2710       if (!isDetailVisible()) {
    2711         hideDetailPane();
    2712       }
    2713     }
    2714   }
    2715
    2716   private void showSearchResults() {
    2717     if (isSearchResultsVisible() && !searchResultsDisplayed && findRule != null) {
    2718       //if pref is set, always update detail panel to contain search results
    2719       detailPanel.removeAll();
    2720       detailPanel.add(searchPane, BorderLayout.CENTER);
    2721       Integer scrollBarWidth = (Integer) UIManager.get("ScrollBar.width");
    2722       JPanel leftSpacePanel = new JPanel();
    2723       leftSpacePanel.setPreferredSize(new Dimension(scrollBarWidth.intValue() -4, -1));
    2724       JPanel rightSpacePanel = new JPanel();
    2725       rightSpacePanel.setPreferredSize(new Dimension(scrollBarWidth.intValue() -4, -1));
    2726       detailPanel.add(leftSpacePanel, BorderLayout.WEST);
    2727       detailPanel.add(rightSpacePanel, BorderLayout.EAST);
    2728       detailPanel.revalidate();
    2729       detailPanel.repaint();
    2730       //if the detail visible pref is not enabled, show the detail pane
    2731       searchResultsDisplayed = true;
    2732       //show if pref is not enabled
    2733       if (!isDetailVisible()) {
    2734         showDetailPane();
    2735       }
    2736     }
    2737   }
    2738
    2739   /**
    2740    * Display the detail pane, using the last known divider location
    2741    */
    2742   private void showDetailPane() {
    2743     if (!isDetailPanelVisible) {
    2744       lowerPanel.setDividerSize(dividerSize);
    2745       if (lowerPanelDividerLocation == 0) {
    2746         lowerPanel.setDividerLocation(DEFAULT_DETAIL_SPLIT_LOCATION);
    2747         lowerPanelDividerLocation = lowerPanel.getDividerLocation();
    2748       } else {
    2749         lowerPanel.setDividerLocation(lowerPanelDividerLocation);
    2750       }
    2751       detailPanel.setVisible(true);
    2752       detailPanel.repaint();
    2753       lowerPanel.repaint();
    2754       isDetailPanelVisible = true;
    2755     }
    2756   }
    2757
    2758   /**
    2759    * Hide the detail pane, holding the current divider location for later use
    2760    */
    2761   private void hideDetailPane() {
    2762     //may be called not currently visible on initial setup to ensure panel is not visible..only update divider location if hiding when currently visible
    2763     if (isDetailPanelVisible) {
    2764       lowerPanelDividerLocation = lowerPanel.getDividerLocation();
    2765     }
    2766     lowerPanel.setDividerSize(0);
    2767     detailPanel.setVisible(false);
    2768     lowerPanel.repaint();
    2769     isDetailPanelVisible = false;
    2770   }
    2771
    2772   /**
    2773    * Display the log tree pane, using the last known divider location
    2774    */
    2775   private void showLogTreePanel() {
    2776     nameTreeAndMainPanelSplit.setDividerSize(dividerSize);
    2777     nameTreeAndMainPanelSplit.setDividerLocation(
    2778       lastLogTreePanelSplitLocation);
    2779     logTreePanel.setVisible(true);
    2780     nameTreeAndMainPanelSplit.repaint();
    2781   }
    2782
    2783   /**
    2784    * Hide the log tree pane, holding the current divider location for later use
    2785    */
    2786   private void hideLogTreePanel() {
    2787     //subtract one to make sizes match
    2788     int currentSize = nameTreeAndMainPanelSplit.getWidth() - nameTreeAndMainPanelSplit.getDividerSize() - 1;
    2789
    2790     if (currentSize > 0) {
    2791       lastLogTreePanelSplitLocation =
    2792         (double) nameTreeAndMainPanelSplit.getDividerLocation() / currentSize;
    2793     }
    2794     nameTreeAndMainPanelSplit.setDividerSize(0);
    2795     logTreePanel.setVisible(false);
    2796     nameTreeAndMainPanelSplit.repaint();
    2797   }
    2798
    2799   /**
    2800    * Return a toolbar used by the undocked LogPanel's frame
    2801    *
    2802    * @return toolbar
    2803    */
    2804   private JToolBar createDockwindowToolbar() {
    2805     final JToolBar toolbar = new JToolBar();
    2806     toolbar.setFloatable(false);
    2807
    2808     final Action dockPauseAction =
    2809       new AbstractAction("Pause") {
    2810         public void actionPerformed(ActionEvent evt) {
    2811           setPaused(!isPaused());
    2812         }
    2813       };
    2814
    2815     dockPauseAction.putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_P));
    2816     dockPauseAction.putValue(
    2817       Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("F12"));
    2818     dockPauseAction.putValue(
    2819       Action.SHORT_DESCRIPTION,
    2820       "Halts the display, while still allowing events to stream in the background");
    2821     dockPauseAction.putValue(
    2822       Action.SMALL_ICON, new ImageIcon(ChainsawIcons.PAUSE));
    2823
    2824     final SmallToggleButton dockPauseButton =
    2825       new SmallToggleButton(dockPauseAction);
    2826     dockPauseButton.setText("");
    2827
    2828     dockPauseButton.getModel().setSelected(isPaused());
    2829
    2830     addPropertyChangeListener(
    2831       "paused",
    2832       new PropertyChangeListener() {
    2833         public void propertyChange(PropertyChangeEvent evt) {
    2834           dockPauseButton.getModel().setSelected(isPaused());
    2835         }
    2836       });
    2837     toolbar.add(dockPauseButton);
    2838
    2839     Action dockShowPrefsAction =
    2840       new AbstractAction("") {
    2841         public void actionPerformed(ActionEvent arg0) {
    2842           showPreferences();
    2843         }
    2844       };
    2845
    2846     dockShowPrefsAction.putValue(
    2847       Action.SHORT_DESCRIPTION, "Define preferences...");
    2848     dockShowPrefsAction.putValue(
    2849       Action.SMALL_ICON, ChainsawIcons.ICON_PREFERENCES);
    2850
    2851     toolbar.add(new SmallButton(dockShowPrefsAction));
    2852
    2853     Action dockToggleLogTreeAction =
    2854       new AbstractAction() {
    2855         public void actionPerformed(ActionEvent e) {
    2856           toggleLogTreeVisible();
    2857         }
    2858       };
    2859
    2860       dockToggleLogTreeAction.putValue(Action.SHORT_DESCRIPTION, "Toggles the Logger Tree Pane");
    2861       dockToggleLogTreeAction.putValue("enabled", Boolean.TRUE);
    2862       dockToggleLogTreeAction.putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_T));
    2863       dockToggleLogTreeAction.putValue(
    2864         Action.ACCELERATOR_KEY,
    2865         KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
    2866       dockToggleLogTreeAction.putValue(
    2867         Action.SMALL_ICON, new ImageIcon(ChainsawIcons.WINDOW_ICON));
    2868
    2869     final SmallToggleButton toggleLogTreeButton =
    2870       new SmallToggleButton(dockToggleLogTreeAction);
    2871     preferenceModel.addPropertyChangeListener("logTreePanelVisible", new PropertyChangeListener() {
    2872     	public void propertyChange(PropertyChangeEvent evt) {
    2873     	    toggleLogTreeButton.setSelected(preferenceModel.isLogTreePanelVisible());
    2874     	}
    2875     });
    2876
    2877     toggleLogTreeButton.setSelected(isLogTreeVisible());
    2878     toolbar.add(toggleLogTreeButton);
    2879     toolbar.addSeparator();
    2880
    2881     final Action undockedClearAction =
    2882       new AbstractAction("Clear") {
    2883         public void actionPerformed(ActionEvent arg0) {
    2884           clearModel();
    2885         }
    2886       };
    2887
    2888     undockedClearAction.putValue(
    2889       Action.SMALL_ICON, new ImageIcon(ChainsawIcons.DELETE));
    2890     undockedClearAction.putValue(
    2891       Action.SHORT_DESCRIPTION, "Removes all the events from the current view");
    2892
    2893     final SmallButton dockClearButton = new SmallButton(undockedClearAction);
    2894     dockClearButton.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
    2895       KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
    2896       undockedClearAction.getValue(Action.NAME));
    2897     dockClearButton.getActionMap().put(
    2898       undockedClearAction.getValue(Action.NAME), undockedClearAction);
    2899
    2900     dockClearButton.setText("");
    2901     toolbar.add(dockClearButton);
    2902     toolbar.addSeparator();
    2903
    2904     Action dockToggleScrollToBottomAction =
    2905         new AbstractAction("Toggles Scroll to Bottom") {
    2906           public void actionPerformed(ActionEvent e) {
    2907             toggleScrollToBottom();
    2908           }
    2909         };
    2910
    2911         dockToggleScrollToBottomAction.putValue(Action.SHORT_DESCRIPTION, "Toggles Scroll to Bottom");
    2912         dockToggleScrollToBottomAction.putValue("enabled", Boolean.TRUE);
    2913         dockToggleScrollToBottomAction.putValue(
    2914           Action.SMALL_ICON, new ImageIcon(ChainsawIcons.SCROLL_TO_BOTTOM));
    2915
    2916       final SmallToggleButton toggleScrollToBottomButton =
    2917         new SmallToggleButton(dockToggleScrollToBottomAction);
    2918       preferenceModel.addPropertyChangeListener("scrollToBottom", new PropertyChangeListener() {
    2919       	public void propertyChange(PropertyChangeEvent evt) {
    2920       	    toggleScrollToBottomButton.setSelected(isScrollToBottom());
    2921       	}
    2922       });
    2923
    2924       toggleScrollToBottomButton.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
    2925   	      KeyStroke.getKeyStroke(KeyEvent.VK_B, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
    2926   	      dockToggleScrollToBottomAction.getValue(Action.NAME));
    2927   	    toggleScrollToBottomButton.getActionMap().put(
    2928   	      dockToggleScrollToBottomAction.getValue(Action.NAME), dockToggleScrollToBottomAction);
    2929
    2930       toggleScrollToBottomButton.setSelected(isScrollToBottom());
    2931       toggleScrollToBottomButton.setText("");
    2932       toolbar.add(toggleScrollToBottomButton);
    2933       toolbar.addSeparator();
    2934
    2935     findCombo.addActionListener(new ActionListener(){
    2936         public void actionPerformed(ActionEvent e) {
    2937           //comboboxchanged event received when text is modified in the field..when enter is pressed, it's comboboxedited
    2938           if (e.getActionCommand().equalsIgnoreCase("comboBoxEdited")) {
    2939               findNext();
    2940           }
    2941         }
    2942     });
    2943     Action redockAction =
    2944       new AbstractAction("", ChainsawIcons.ICON_DOCK) {
    2945         public void actionPerformed(ActionEvent arg0) {
    2946           dock();
    2947         }
    2948       };
    2949
    2950     redockAction.putValue(
    2951       Action.SHORT_DESCRIPTION,
    2952       "Docks this window back with the main Chainsaw window");
    2953
    2954     SmallButton redockButton = new SmallButton(redockAction);
    2955     toolbar.add(redockButton);
    2956
    2957     return toolbar;
    2958   }
    2959
    2960   /**
    2961    * Update the status bar with current selected row and row count
    2962    */
    2963   protected void updateStatusBar() {
    2964     SwingHelper.invokeOnEDT(
    2965       new Runnable() {
    2966         public void run() {
    2967           statusBar.setSelectedLine(
    2968             table.getSelectedRow() + 1, tableModel.getRowCount(),
    2969             tableModel.size(), getIdentifier());
    2970           statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
    2971         }
    2972       });
    2973   }
    2974
    2975   /**
    2976    * Update the detail pane layout text
    2977    *
    2978    * @param conversionPattern layout text
    2979    */
    2980   private void setDetailPaneConversionPattern(String conversionPattern) {
    2981     String oldPattern = getDetailPaneConversionPattern();
    2982     (detailLayout).setConversionPattern(conversionPattern);
    2983     firePropertyChange(
    2984       "detailPaneConversionPattern", oldPattern,
    2985       getDetailPaneConversionPattern());
    2986   }
    2987
    2988   /**
    2989    * Accessor
    2990    *
    2991    * @return conversionPattern layout text
    2992    */
    2993   private String getDetailPaneConversionPattern() {
    2994     return (detailLayout).getConversionPattern();
    2995   }
    2996
    2997   /**
    2998    * Reset the LoggingEvent container, detail panel and status bar
    2999    */
    3000   private void clearModel() {
    3001     previousLastIndex = -1;
    3002     tableModel.clearModel();
    3003     searchModel.clearModel();
    3004
    3005     synchronized (detail) {
    3006       detailPaneUpdater.setSelectedRow(-1);
    3007       detail.notify();
    3008     }
    3009
    3010     statusBar.setNothingSelected();
    3011   }
    3012
    3013   public void findNextColorizedEvent() {
    3014     EventQueue.invokeLater(new Runnable() {
    3015         public void run() {
    3016             final int nextRow = tableModel.findColoredRow(table.getSelectedRow() + 1, true);
    3017             if (nextRow > -1) {
    3018                 table.scrollToRow(nextRow);
    3019             }
    3020         }
    3021     });
    3022   }
    3023
    3024   public void findPreviousColorizedEvent() {
    3025     EventQueue.invokeLater(new Runnable() {
    3026         public void run() {
    3027             final int previousRow = tableModel.findColoredRow(table.getSelectedRow() - 1, false);
    3028             if (previousRow > -1) {
    3029                 table.scrollToRow(previousRow);
    3030             }
    3031         }
    3032     });
    3033   }
    3034
    3035   /**
    3036    * Finds the next row matching the current find rule, and ensures it is made
    3037    * visible
    3038    *
    3039    */
    3040   public void findNext() {
    3041     Object item = findCombo.getSelectedItem();
    3042     updateFindRule(item == null ? null: item.toString());
    3043
    3044     if (findRule != null) {
    3045         EventQueue.invokeLater(new Runnable() {
    3046             public void run() {
    3047               final JTextField findText =(JTextField) findCombo.getEditor().getEditorComponent();
    3048                 try {
    3049                   int filteredEventsSize = getFilteredEvents().size();
    3050                   int startRow = table.getSelectedRow() + 1;
    3051                     if (startRow > filteredEventsSize - 1) {
    3052                         startRow = 0;
    3053                     }
    3054                   //no selected row would return -1, so we'd start at row zero
    3055                   final int nextRow = tableModel.locate(findRule, startRow, true);
    3056
    3057                   if (nextRow > -1) {
    3058                     table.scrollToRow(nextRow);
    3059                     findText.setToolTipText("Enter an expression - right click or ctrl-space for menu - press enter to add to list");
    3060                   }
    3061                   findText.setBackground(UIManager.getColor("TextField.background"));
    3062                 } catch (IllegalArgumentException iae) {
    3063                   findText.setToolTipText(iae.getMessage());
    3064                   findText.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
    3065                   colorizer.setFindRule(null);
    3066                   tableRuleMediator.setFindRule(null);
    3067                   searchRuleMediator.setFindRule(null);
    3068                 }
    3069             }
    3070         });
    3071     }
    3072   }
    3073
    3074   /**
    3075    * Finds the previous row matching the current find rule, and ensures it is made
    3076    * visible
    3077    *
    3078    */
    3079   public void findPrevious() {
    3080     Object item = findCombo.getSelectedItem();
    3081     updateFindRule(item == null ? null: item.toString());
    3082
    3083     if (findRule != null) {
    3084         EventQueue.invokeLater(new Runnable() {
    3085             public void run() {
    3086               final JTextField findText =(JTextField) findCombo.getEditor().getEditorComponent();
    3087                 try {
    3088                     int startRow = table.getSelectedRow() - 1;
    3089                     int filteredEventsSize = getFilteredEvents().size();
    3090                     if (startRow < 0) {
    3091                         startRow = filteredEventsSize - 1;
    3092                     }
    3093                     final int previousRow = tableModel.locate(findRule, startRow, false);
    3094
    3095                     if (previousRow > -1) {
    3096                         table.scrollToRow(previousRow);
    3097                         findCombo.setToolTipText("Enter an expression - right click or ctrl-space for menu - press enter to add to list");
    3098                     }
    3099                   findText.setBackground(UIManager.getColor("TextField.background"));
    3100                 } catch (IllegalArgumentException iae) {
    3101                   findText.setToolTipText(iae.getMessage());
    3102                   findText.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
    3103                 }
    3104             }
    3105         });
    3106     }
    3107   }
    3108
    3109   /**
    3110    * Docks this DockablePanel by hiding the JFrame and placing the Panel back
    3111    * inside the LogUI window.
    3112    */
    3113   private void dock() {
    3114
    3115   	final int row = table.getSelectedRow();
    3116     setDocked(true);
    3117     undockedFrame.setVisible(false);
    3118     removeAll();
    3119
    3120     add(nameTreeAndMainPanelSplit, BorderLayout.CENTER);
    3121     externalPanel.setDocked(true);
    3122     dockingAction.putValue(Action.NAME, "Undock");
    3123     dockingAction.putValue(Action.SMALL_ICON, ChainsawIcons.ICON_UNDOCK);
    3124     if (row > -1) {
    3125         EventQueue.invokeLater(new Runnable() {
    3126             public void run() {
    3127                 table.scrollToRow(row);
    3128             }
    3129         });
    3130     }
    3131   }
    3132
    3133   /**
    3134    * Load default column settings if no settings exist for this identifier
    3135    *
    3136    * @param event
    3137    */
    3138   private void loadDefaultColumnSettings(LoadSettingsEvent event) {
    3139     String columnOrder = event.getSetting(TABLE_COLUMN_ORDER);
    3140
    3141     TableColumnModel columnModel = table.getColumnModel();
    3142     TableColumnModel searchColumnModel = searchTable.getColumnModel();
    3143
    3144     Map columnNameMap = new HashMap();
    3145     Map searchColumnNameMap = new HashMap();
    3146
    3147     for (int i = 0; i < columnModel.getColumnCount(); i++) {
    3148       columnNameMap.put(table.getColumnName(i).toUpperCase(), columnModel.getColumn(i));
    3149     }
    3150
    3151     for (int i = 0; i < searchColumnModel.getColumnCount(); i++) {
    3152       searchColumnNameMap.put(searchTable.getColumnName(i).toUpperCase(), searchColumnModel.getColumn(i));
    3153     }
    3154
    3155     int index = 0;
    3156     StringTokenizer tok = new StringTokenizer(columnOrder, ",");
    3157     List sortedColumnList = new ArrayList();
    3158
    3159     /*
    3160        remove all columns from the table that exist in the model
    3161        and add in the correct order to a new arraylist
    3162        (may be a subset of possible columns)
    3163      **/
    3164     while (tok.hasMoreElements()) {
    3165       String element = tok.nextElement().toString().trim().toUpperCase();
    3166       TableColumn column = (TableColumn) columnNameMap.get(element);
    3167
    3168       if (column != null) {
    3169         sortedColumnList.add(column);
    3170         table.removeColumn(column);
    3171         searchTable.removeColumn(column);
    3172       }
    3173     }
    3174     preferenceModel.setDetailPaneVisible(event.asBoolean("detailPaneVisible"));
    3175     preferenceModel.setLogTreePanelVisible(event.asBoolean("logTreePanelVisible"));
    3176     preferenceModel.setHighlightSearchMatchText(event.asBoolean("highlightSearchMatchText"));
    3177     preferenceModel.setWrapMessage(event.asBoolean("wrapMessage"));
    3178     preferenceModel.setSearchResultsVisible(event.asBoolean("searchResultsVisible"));
    3179     //re-add columns to the table in the order provided from the list
    3180     for (Iterator iter = sortedColumnList.iterator(); iter.hasNext();) {
    3181       TableColumn element = (TableColumn) iter.next();
    3182       if (preferenceModel.addColumn(element)) {
    3183           if (!applicationPreferenceModel.isDefaultColumnsSet() || applicationPreferenceModel.isDefaultColumnsSet() &&
    3184               applicationPreferenceModel.getDefaultColumnNames().contains(element.getHeaderValue())) {
    3185             table.addColumn(element);
    3186             searchTable.addColumn(element);
    3187             preferenceModel.setColumnVisible(element.getHeaderValue().toString(), true);
    3188           }
    3189       }
    3190     }
    3191
    3192     String columnWidths = event.getSetting(TABLE_COLUMN_WIDTHS);
    3193
    3194     tok = new StringTokenizer(columnWidths, ",");
    3195     index = 0;
    3196
    3197     while (tok.hasMoreElements()) {
    3198       String element = (String) tok.nextElement();
    3199
    3200       try {
    3201         int width = Integer.parseInt(element);
    3202
    3203         if (index > (columnModel.getColumnCount() - 1)) {
    3204           logger.warn(
    3205             "loadsettings - failed attempt to set width for index " + index
    3206             + ", width " + element);
    3207         } else {
    3208           columnModel.getColumn(index).setPreferredWidth(width);
    3209           searchColumnModel.getColumn(index).setPreferredWidth(width);
    3210         }
    3211
    3212         index++;
    3213       } catch (NumberFormatException e) {
    3214         logger.error("Error decoding a Table width", e);
    3215       }
    3216     }
    3217     undockedFrame.setSize(getSize());
    3218     undockedFrame.setLocation(getBounds().x, getBounds().y);
    3219
    3220       repaint();
    3221     }
    3222
    3223   /**
    3224    * Iterate over all values in the column and return the longest width
    3225    *
    3226    * @param index column index
    3227    *
    3228    * @return longest width - relies on FontMetrics.stringWidth for calculation
    3229    */
    3230   private int getMaxColumnWidth(int index) {
    3231     FontMetrics metrics = getGraphics().getFontMetrics();
    3232     int longestWidth =
    3233       metrics.stringWidth("  " + table.getColumnName(index) + "  ")
    3234       + (2 * table.getColumnModel().getColumnMargin());
    3235
    3236     for (int i = 0, j = tableModel.getRowCount(); i < j; i++) {
    3237       Component c =
    3238         renderer.getTableCellRendererComponent(
    3239           table, table.getValueAt(i, index), false, false, i, index);
    3240
    3241       if (c instanceof JLabel) {
    3242         longestWidth =
    3243           Math.max(longestWidth, metrics.stringWidth(((JLabel) c).getText()));
    3244       }
    3245     }
    3246
    3247     return longestWidth + 5;
    3248   }
    3249
    3250   private String getToolTipTextForEvent(LoggingEventWrapper loggingEventWrapper) {
    3251     StringBuffer buf = new StringBuffer();
    3252     buf.append(detailLayout.getHeader()).append(detailLayout.format(loggingEventWrapper.getLoggingEvent())).append(detailLayout.getFooter());
    3253     return buf.toString();
    3254   }
    3255
    3256   /**
    3257    * ensures the Entry map of all the unque logger names etc, that is used for
    3258    * the Filter panel is updated with any new information from the event
    3259    *
    3260    * @param event
    3261    */
    3262   private void updateOtherModels(LoggingEvent event) {
    3263
    3264     /*
    3265      * EventContainer is a LoggerNameModel imp, use that for notifing
    3266      */
    3267     tableModel.addLoggerName(event.getLoggerName());
    3268
    3269     filterModel.processNewLoggingEvent(event);
    3270   }
    3271
    3272     public void findNextMarker() {
    3273       EventQueue.invokeLater(new Runnable() {
    3274           public void run() {
    3275               int startRow = table.getSelectedRow() + 1;
    3276               int filteredEventsSize = getFilteredEvents().size();
    3277               if (startRow > filteredEventsSize - 1) {
    3278                   startRow = 0;
    3279               }
    3280               final int nextRow = tableModel.locate(findMarkerRule, startRow, true);
    3281
    3282               if (nextRow > -1) {
    3283                   table.scrollToRow(nextRow);
    3284               }
    3285           }
    3286       });
    3287     }
    3288
    3289     public void findPreviousMarker() {
    3290         EventQueue.invokeLater(new Runnable() {
    3291             public void run() {
    3292                 int startRow = table.getSelectedRow() - 1;
    3293                 int filteredEventsSize = getFilteredEvents().size();
    3294                 if (startRow < 0) {
    3295                     startRow = filteredEventsSize - 1;
    3296                 }
    3297                 final int previousRow = tableModel.locate(findMarkerRule, startRow, false);
    3298
    3299                 if (previousRow > -1) {
    3300                     table.scrollToRow(previousRow);
    3301                 }
    3302             }
    3303         });
    3304     }
    3305
    3306     public void clearAllMarkers() {
    3307       //this will get the properties to be removed from both tables..but
    3308       tableModel.removePropertyFromEvents(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
    3309     }
    3310
    3311     public void toggleMarker() {
    3312         int row = table.getSelectedRow();
    3313         if (row != -1) {
    3314           LoggingEventWrapper loggingEventWrapper = tableModel.getRow(row);
    3315           if (loggingEventWrapper != null) {
    3316               Object marker = loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
    3317               if (marker == null) {
    3318                   loggingEventWrapper.setProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE, "set");
    3319               } else {
    3320                   loggingEventWrapper.removeProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
    3321               }
    3322               //if marker -was- null, it no longer is (may need to add the column)
    3323               tableModel.fireRowUpdated(row, (marker == null));
    3324           }
    3325         }
    3326     }
    3327
    3328     public void layoutComponents()
    3329     {
    3330         if (preferenceModel.isDetailPaneVisible()) {
    3331           showDetailPane();
    3332          } else {
    3333           hideDetailPane();
    3334         }
    3335     }
    3336
    3337   public void setFindText(String findText) {
    3338     findCombo.setSelectedItem(findText);
    3339     findNext();
    3340   }
    3341
    3342   public String getFindText() {
    3343     Object selectedItem = findCombo.getSelectedItem();
    3344     if (selectedItem == null) {
    3345       return "";
    3346     }
    3347     return selectedItem.toString();
    3348   }
    3349
    3350   /**
    3351    * This class receives notification when the Refine focus or find field is
    3352    * updated, where a background thread periodically wakes up and checks if
    3353    * they have stopped typing yet. This ensures that the filtering of the
    3354    * model is not done for every single character typed.
    3355    *
    3356    * @author Paul Smith psmith
    3357    */
    3358   private final class DelayedTextDocumentListener
    3359     implements DocumentListener {
    3360     private static final long CHECK_PERIOD = 1000;
    3361     private final JTextField textField;
    3362     private long lastTimeStamp = System.currentTimeMillis();
    3363     private final Thread delayThread;
    3364     private final String defaultToolTip;
    3365     private String lastText = "";
    3366
    3367     private DelayedTextDocumentListener(final JTextField textFeld) {
    3368       super();
    3369       this.textField = textFeld;
    3370       this.defaultToolTip = textFeld.getToolTipText();
    3371
    3372       this.delayThread =
    3373         new Thread(
    3374           new Runnable() {
    3375             public void run() {
    3376               while (true) {
    3377                 try {
    3378                   Thread.sleep(CHECK_PERIOD);
    3379                 } catch (InterruptedException e) {
    3380                 }
    3381
    3382                 if (
    3383                   (System.currentTimeMillis() - lastTimeStamp) < CHECK_PERIOD) {
    3384                   // They typed something since the last check. we ignor
    3385                   // this for a sample period
    3386                   //                logger.debug("Typed something since the last check");
    3387                 } else if (
    3388                   (System.currentTimeMillis() - lastTimeStamp) < (2 * CHECK_PERIOD)) {
    3389                   // they stopped typing recently, but have stopped for at least
    3390                   // 1 sample period. lets apply the filter
    3391                   //                logger.debug("Typed something recently applying filter");
    3392                   if (!(textFeld.getText().trim().equals(lastText.trim()))) {
    3393                     lastText = textFeld.getText();
    3394                     EventQueue.invokeLater(new Runnable()
    3395                     {
    3396                         public void run()
    3397                         {
    3398                           setFilter();
    3399                         }
    3400                     });
    3401                   }
    3402                 } else {
    3403                   // they stopped typing a while ago, let's forget about it
    3404                   //                logger.debug(
    3405                   //                  "They stoppped typing a while ago, assuming filter has been applied");
    3406                 }
    3407               }
    3408             }
    3409           });
    3410
    3411       delayThread.setPriority(Thread.MIN_PRIORITY);
    3412       delayThread.start();
    3413     }
    3414
    3415     /**
    3416      * Update timestamp
    3417      *
    3418      * @param e
    3419      */
    3420     public void insertUpdate(DocumentEvent e) {
    3421       notifyChange();
    3422     }
    3423
    3424     /**
    3425      * Update timestamp
    3426      *
    3427      * @param e
    3428      */
    3429     public void removeUpdate(DocumentEvent e) {
    3430       notifyChange();
    3431     }
    3432
    3433     /**
    3434      * Update timestamp
    3435      *
    3436      * @param e
    3437      */
    3438     public void changedUpdate(DocumentEvent e) {
    3439       notifyChange();
    3440     }
    3441
    3442     /**
    3443      * Update timestamp
    3444      */
    3445     private void notifyChange() {
    3446       this.lastTimeStamp = System.currentTimeMillis();
    3447     }
    3448
    3449     /**
    3450      * Update refinement rule based on the entered expression.
    3451      */
    3452     private void setFilter() {
    3453       if (textField.getText().trim().equals("")) {
    3454         //reset background color in case we were previously an invalid expression
    3455         textField.setBackground(UIManager.getColor("TextField.background"));
    3456         tableRuleMediator.setFilterRule(null);
    3457         searchRuleMediator.setFilterRule(null);
    3458         textField.setToolTipText(defaultToolTip);
    3459         if (findRule != null) {
    3460           currentSearchMatchCount=tableModel.getSearchMatchCount();
    3461           statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
    3462         }
    3463       } else {
    3464         try {
    3465           tableRuleMediator.setFilterRule(ExpressionRule.getRule(textField.getText()));
    3466           searchRuleMediator.setFilterRule(ExpressionRule.getRule(textField.getText()));
    3467           textField.setToolTipText(defaultToolTip);
    3468           if (findRule != null) {
    3469             currentSearchMatchCount=tableModel.getSearchMatchCount();
    3470             statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
    3471           }
    3472           //valid expression, reset background color in case we were previously an invalid expression
    3473           textField.setBackground(UIManager.getColor("TextField.background"));
    3474         } catch (IllegalArgumentException iae) {
    3475           //invalid expression, change background of the field
    3476           textField.setToolTipText(iae.getMessage());
    3477           textField.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
    3478           if (findRule != null) {
    3479             currentSearchMatchCount=tableModel.getSearchMatchCount();
    3480             statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
    3481           }
    3482         }
    3483       }
    3484     }
    3485   }
    3486
    3487   private final class TableMarkerListener extends MouseAdapter {
    3488     private JTable markerTable;
    3489     private EventContainer markerEventContainer;
    3490     private EventContainer otherMarkerEventContainer;
    3491
    3492     private TableMarkerListener(JTable markerTable, EventContainer markerEventContainer, EventContainer otherMarkerEventContainer) {
    3493       this.markerTable = markerTable;
    3494       this.markerEventContainer = markerEventContainer;
    3495       this.otherMarkerEventContainer = otherMarkerEventContainer;
    3496     }
    3497
    3498       public void mouseClicked(MouseEvent evt) {
    3499           if (evt.getClickCount() == 2) {
    3500               int row = markerTable.rowAtPoint(evt.getPoint());
    3501               if (row != -1) {
    3502                 LoggingEventWrapper loggingEventWrapper = markerEventContainer.getRow(row);
    3503                 if (loggingEventWrapper != null) {
    3504                     Object marker = loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
    3505                     if (marker == null) {
    3506                         loggingEventWrapper.setProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE, "set");
    3507                     } else {
    3508                         loggingEventWrapper.removeProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
    3509                     }
    3510                     //if marker -was- null, it no longer is (may need to add the column)
    3511                     markerEventContainer.fireRowUpdated(row, (marker == null));
    3512                     otherMarkerEventContainer.fireRowUpdated(otherMarkerEventContainer.getRowIndex(loggingEventWrapper), (marker == null));
    3513                 }
    3514               }
    3515           }
    3516       }
    3517   }
    3518
    3519   /**
    3520    * Update active tooltip
    3521    */
    3522   private final class TableColumnDetailMouseListener extends MouseMotionAdapter {
    3523     private int currentRow = -1;
    3524     private JTable detailTable;
    3525     private EventContainer detailEventContainer;
    3526
    3527     private TableColumnDetailMouseListener(JTable detailTable, EventContainer detailEventContainer) {
    3528       this.detailTable = detailTable;
    3529       this.detailEventContainer = detailEventContainer;
    3530     }
    3531
    3532     /**
    3533      * Update tooltip based on mouse position
    3534      *
    3535      * @param evt
    3536      */
    3537     public void mouseMoved(MouseEvent evt) {
    3538       currentPoint = evt.getPoint();
    3539       currentTable = detailTable;
    3540
    3541       if (preferenceModel.isToolTips()) {
    3542         int row = detailTable.rowAtPoint(evt.getPoint());
    3543
    3544         if ((row == currentRow) || (row == -1)) {
    3545           return;
    3546         }
    3547
    3548         currentRow = row;
    3549
    3550         LoggingEventWrapper event = detailEventContainer.getRow(currentRow);
    3551
    3552         if (event != null) {
    3553           String toolTipText = getToolTipTextForEvent(event);
    3554           detailTable.setToolTipText(toolTipText);
    3555         }
    3556       } else {
    3557         detailTable.setToolTipText(null);
    3558       }
    3559     }
    3560   }
    3561
    3562   //if columnmoved or columnremoved callback received, re-apply table's sort index based
    3563   //sort column name
    3564   private class ChainsawTableColumnModelListener implements TableColumnModelListener {
    3565     private JSortTable modelListenerTable;
    3566
    3567     private ChainsawTableColumnModelListener(JSortTable modelListenerTable) {
    3568       this.modelListenerTable = modelListenerTable;
    3569     }
    3570
    3571     public void columnAdded(TableColumnModelEvent e) {
    3572       //no-op
    3573     }
    3574
    3575     /**
    3576      * Update sorted column
    3577      *
    3578      * @param e
    3579      */
    3580     public void columnRemoved(TableColumnModelEvent e) {
    3581       modelListenerTable.updateSortedColumn();
    3582     }
    3583
    3584     /**
    3585      * Update sorted column
    3586      *
    3587      * @param e
    3588      */
    3589     public void columnMoved(TableColumnModelEvent e) {
    3590       modelListenerTable.updateSortedColumn();
    3591     }
    3592
    3593     /**
    3594      * Ignore margin changed
    3595      *
    3596      * @param e
    3597      */
    3598     public void columnMarginChanged(ChangeEvent e) {
    3599     }
    3600
    3601     /**
    3602      * Ignore selection changed
    3603      *
    3604      * @param e
    3605      */
    3606     public void columnSelectionChanged(ListSelectionEvent e) {
    3607     }
    3608   }
    3609
    3610   /**
    3611    * Thread that periodically checks if the selected row has changed, and if
    3612    * it was, updates the Detail Panel with the detailed Logging information
    3613    */
    3614   private class DetailPaneUpdater implements PropertyChangeListener {
    3615     private int selectedRow = -1;
    3616     int lastRow = -1;
    3617     private DetailPaneUpdater() {
    3618     }
    3619
    3620     /**
    3621      * Update detail pane to display information about the LoggingEvent at index row
    3622      *
    3623      * @param row
    3624      */
    3625     private void setSelectedRow(int row) {
    3626       selectedRow = row;
    3627       updateDetailPane();
    3628     }
    3629
    3630     private void setAndUpdateSelectedRow(int row) {
    3631         selectedRow = row;
    3632         updateDetailPane(true);
    3633     }
    3634
    3635     private void updateDetailPane() {
    3636         updateDetailPane(false);
    3637     }
    3638     /**
    3639      * Update detail pane
    3640      */
    3641     private void updateDetailPane(boolean force) {
    3642             /*
    3643              * Don't bother doing anything if it's not visible. Note: the isVisible() method on
    3644              * Component is not really accurate here because when the button to toggle display of
    3645              * the detail pane is triggered it still appears as 'visible' for some reason.
    3646              */
    3647       if (!preferenceModel.isDetailPaneVisible()) {
    3648         return;
    3649       }
    3650
    3651 	      LoggingEventWrapper loggingEventWrapper = null;
    3652 	      if (force || (selectedRow != -1 && (lastRow != selectedRow))) {
    3653 	        loggingEventWrapper = tableModel.getRow(selectedRow);
    3654
    3655 	        if (loggingEventWrapper != null) {
    3656 	          final StringBuffer buf = new StringBuffer();
    3657 	          buf.append(detailLayout.getHeader())
    3658 	             .append(detailLayout.format(loggingEventWrapper.getLoggingEvent())).append(
    3659 	            detailLayout.getFooter());
    3660 	          if (buf.length() > 0) {
    3661 		          	try {
    3662 		          		final Document doc = detail.getEditorKit().createDefaultDocument();
    3663 		          		detail.getEditorKit().read(new StringReader(buf.toString()), doc, 0);
    3664
    3665 				      	SwingHelper.invokeOnEDT(new Runnable() {
    3666 				      		public void run() {
    3667 				      			detail.setDocument(doc);
    3668                                 JTextComponentFormatter.applySystemFontAndSize(detail);
    3669 				      			detail.setCaretPosition(0);
    3670                                 lastRow = selectedRow;
    3671 				      		}
    3672 				      	});
    3673 		          	} catch (Exception e) {}
    3674 	      		}
    3675 	        }
    3676 	      }
    3677
    3678 	      if (loggingEventWrapper == null && (lastRow != selectedRow)) {
    3679           	try {
    3680           		final Document doc = detail.getEditorKit().createDefaultDocument();
    3681           		detail.getEditorKit().read(new StringReader("<html>Nothing selected</html>"), doc, 0);
    3682 		      	SwingHelper.invokeOnEDT(new Runnable() {
    3683 		      		public void run() {
    3684 		      			detail.setDocument(doc);
    3685                         JTextComponentFormatter.applySystemFontAndSize(detail);
    3686 		      			detail.setCaretPosition(0);
    3687                         lastRow = selectedRow;
    3688 		      		}
    3689 		      	});
    3690           	} catch (Exception e) {}
    3691   		}
    3692     }
    3693
    3694     /**
    3695      * Update detail pane layout if it's changed
    3696      *
    3697      * @param arg0
    3698      */
    3699     public void propertyChange(PropertyChangeEvent arg0) {
    3700       SwingUtilities.invokeLater(
    3701         new Runnable() {
    3702           public void run() {
    3703             updateDetailPane(true);
    3704           }
    3705         });
    3706     }
    3707   }
    3708     private class ThrowableDisplayMouseAdapter extends MouseAdapter {
    3709       private JTable throwableTable;
    3710       private EventContainer throwableEventContainer;
    3711       final JDialog detailDialog;
    3712       final JEditorPane detailArea;
    3713       public ThrowableDisplayMouseAdapter(JTable throwableTable, EventContainer throwableEventContainer) {
    3714         this.throwableTable = throwableTable;
    3715         this.throwableEventContainer = throwableEventContainer;
    3716
    3717         detailDialog = new JDialog((JFrame) null, true);
    3718         Container container = detailDialog.getContentPane();
    3719         detailArea = new JEditorPane();
    3720         JTextComponentFormatter.applySystemFontAndSize(detailArea);
    3721         detailArea.setEditable(false);
    3722         Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
    3723         detailArea.setPreferredSize(new Dimension(screenDimension.width / 2, screenDimension.height / 2));
    3724         container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
    3725         container.add(new JScrollPane(detailArea));
    3726
    3727         detailDialog.pack();
    3728       }
    3729         public void mouseClicked(MouseEvent e)
    3730         {
    3731             TableColumn column = throwableTable.getColumnModel().getColumn(throwableTable.columnAtPoint(e.getPoint()));
    3732             if (!column.getHeaderValue().toString().toUpperCase().equals(ChainsawColumns.getColumnName(ChainsawColumns.INDEX_THROWABLE_COL_NAME))) {
    3733                 return;
    3734             }
    3735
    3736             LoggingEventWrapper loggingEventWrapper = throwableEventContainer.getRow(throwableTable.getSelectedRow());
    3737
    3738             //throwable string representation may be a length-one empty array
    3739             String[] ti = loggingEventWrapper.getLoggingEvent().getThrowableStrRep();
    3740             if (ti != null && ti.length > 0 && (!(ti.length == 1 && ti[0].equals("")))) {
    3741                  detailDialog.setTitle(throwableTable.getColumnName(throwableTable.getSelectedColumn()) + " detail...");
    3742                   StringBuffer buf = new StringBuffer();
    3743                   buf.append(loggingEventWrapper.getLoggingEvent().getMessage());
    3744                   buf.append("\n");
    3745                   for (int i = 0; i < ti.length; i++) {
    3746                     buf.append(ti[i]).append("\n    ");
    3747                   }
    3748
    3749                   detailArea.setText(buf.toString());
    3750                   SwingHelper.invokeOnEDT(new Runnable() {
    3751                     public void run() {
    3752                       centerAndSetVisible(detailDialog);
    3753                     }
    3754                   });
    3755                 }
    3756         }
    3757     }
    3758
    3759     private class MarkerCellEditor implements TableCellEditor {
    3760       JTable currentTable;
    3761       JTextField textField = new JTextField();
    3762       Set cellEditorListeners = new HashSet();
    3763       private LoggingEventWrapper currentLoggingEventWrapper;
    3764       private final Object mutex = new Object();
    3765
    3766         public Object getCellEditorValue()
    3767         {
    3768             return textField.getText();
    3769         }
    3770
    3771         public boolean isCellEditable(EventObject anEvent)
    3772         {
    3773             return true;
    3774         }
    3775
    3776         public boolean shouldSelectCell(EventObject anEvent)
    3777         {
    3778             textField.selectAll();
    3779             return true;
    3780         }
    3781
    3782         public boolean stopCellEditing()
    3783         {
    3784             if (textField.getText().trim().equals("")) {
    3785                 currentLoggingEventWrapper.removeProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
    3786             } else {
    3787                 currentLoggingEventWrapper.setProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE, textField.getText());
    3788             }
    3789             //row should always exist in the main table if it is being edited
    3790             tableModel.fireRowUpdated(tableModel.getRowIndex(currentLoggingEventWrapper), true);
    3791             int index = searchModel.getRowIndex(currentLoggingEventWrapper);
    3792             if (index > -1) {
    3793               searchModel.fireRowUpdated(index, true);
    3794             }
    3795
    3796             ChangeEvent event = new ChangeEvent(currentTable);
    3797             Set cellEditorListenersCopy;
    3798             synchronized(mutex) {
    3799                 cellEditorListenersCopy = new HashSet(cellEditorListeners);
    3800             }
    3801
    3802             for (Iterator iter = cellEditorListenersCopy.iterator();iter.hasNext();) {
    3803                 ((CellEditorListener)iter.next()).editingStopped(event);
    3804             }
    3805             currentLoggingEventWrapper = null;
    3806             currentTable = null;
    3807
    3808             return true;
    3809         }
    3810
    3811         public void cancelCellEditing()
    3812         {
    3813             Set cellEditorListenersCopy;
    3814             synchronized(mutex) {
    3815                 cellEditorListenersCopy = new HashSet(cellEditorListeners);
    3816             }
    3817
    3818            ChangeEvent event = new ChangeEvent(currentTable);
    3819            for (Iterator iter = cellEditorListenersCopy.iterator();iter.hasNext();) {
    3820                ((CellEditorListener)iter.next()).editingCanceled(event);
    3821            }
    3822           currentLoggingEventWrapper = null;
    3823           currentTable = null;
    3824         }
    3825
    3826         public void addCellEditorListener(CellEditorListener l)
    3827         {
    3828             synchronized(mutex) {
    3829                 cellEditorListeners.add(l);
    3830             }
    3831         }
    3832
    3833         public void removeCellEditorListener(CellEditorListener l)
    3834         {
    3835             synchronized(mutex) {
    3836                 cellEditorListeners.remove(l);
    3837             }
    3838         }
    3839
    3840         public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
    3841         {
    3842           currentTable = table;
    3843           currentLoggingEventWrapper =((EventContainer) table.getModel()).getRow(row);
    3844             if (currentLoggingEventWrapper != null) {
    3845                 textField.setText(currentLoggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE));
    3846                 textField.selectAll();
    3847             }
    3848             else {
    3849               textField.setText("");
    3850             }
    3851             return textField;
    3852         }
    3853     }
    3854
    3855     private class EventTimeDeltaMatchThumbnail extends AbstractEventMatchThumbnail {
    3856         public EventTimeDeltaMatchThumbnail() {
    3857             super();
    3858             initializeLists();
    3859         }
    3860
    3861         boolean primaryMatches(ThumbnailLoggingEventWrapper wrapper) {
    3862             String millisDelta = wrapper.loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE);
    3863             if (millisDelta != null && !millisDelta.trim().equals("")) {
    3864                 long millisDeltaLong = Long.parseLong(millisDelta);
    3865                 //arbitrary
    3866                 return millisDeltaLong >= 1000;
    3867             }
    3868             return false;
    3869         }
    3870
    3871         boolean secondaryMatches(ThumbnailLoggingEventWrapper wrapper) {
    3872             //secondary is not used
    3873             return false;
    3874         }
    3875
    3876         private void initializeLists() {
    3877             secondaryList.clear();
    3878             primaryList.clear();
    3879
    3880             int i=0;
    3881             for (Iterator iter = tableModel.getFilteredEvents().iterator();iter.hasNext();) {
    3882                 LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper) iter.next();
    3883                 ThumbnailLoggingEventWrapper wrapper = new ThumbnailLoggingEventWrapper(i, loggingEventWrapper);
    3884                 i++;
    3885                 //only add if there is a color defined
    3886                 if (primaryMatches(wrapper)) {
    3887                     primaryList.add(wrapper);
    3888                 }
    3889             }
    3890             revalidate();
    3891             repaint();
    3892         }
    3893
    3894         public void paintComponent(Graphics g) {
    3895             super.paintComponent(g);
    3896
    3897             int rowCount = table.getRowCount();
    3898             if (rowCount == 0) {
    3899                 return;
    3900             }
    3901             //use event pane height as reference height - max component height will be extended by event height if
    3902             // last row is rendered, so subtract here
    3903             int height = eventsPane.getHeight();
    3904             int maxHeight = Math.min(maxEventHeight, (height / rowCount));
    3905             int minHeight = Math.max(1, maxHeight);
    3906             int componentHeight = height - minHeight;
    3907             int eventHeight = minHeight;
    3908
    3909             //draw all events
    3910             for (Iterator iter = primaryList.iterator();iter.hasNext();) {
    3911                 ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper)iter.next();
    3912                     if (primaryMatches(wrapper)) {
    3913                         float ratio = (wrapper.rowNum / (float)rowCount);
    3914         //                System.out.println("error - ratio: " + ratio + ", component height: " + componentHeight);
    3915                         int verticalLocation = (int) (componentHeight * ratio);
    3916
    3917                         int startX = 1;
    3918                         int width = getWidth() - (startX * 2);
    3919                         //max out at 50, min 2...
    3920                         String millisDelta = wrapper.loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE);
    3921                         long millisDeltaLong = Long.parseLong(millisDelta);
    3922                         long delta = Math.min(ChainsawConstants.MILLIS_DELTA_RENDERING_HEIGHT_MAX, Math.max(0, (long) (millisDeltaLong * ChainsawConstants.MILLIS_DELTA_RENDERING_FACTOR)));
    3923                         float widthMaxMillisDeltaRenderRatio = ((float)width / ChainsawConstants.MILLIS_DELTA_RENDERING_HEIGHT_MAX);
    3924                         int widthToUse = Math.max(2, (int)(delta * widthMaxMillisDeltaRenderRatio));
    3925                         eventHeight = Math.min(maxEventHeight, eventHeight + 3);
    3926 //                            eventHeight = maxEventHeight;
    3927                         drawEvent(applicationPreferenceModel.getDeltaColor(), (verticalLocation - eventHeight + 1), eventHeight, g, startX, widthToUse);
    3928     //                System.out.println("painting error - rownum: " + wrapper.rowNum + ", location: " + verticalLocation + ", height: " + eventHeight + ", component height: " + componentHeight + ", row count: " + rowCount);
    3929                 }
    3930             }
    3931         }
    3932     }
    3933
    3934   //a listener receiving color updates needs to call configureColors on this class
    3935     private class ColorizedEventAndSearchMatchThumbnail extends AbstractEventMatchThumbnail {
    3936         public ColorizedEventAndSearchMatchThumbnail() {
    3937             super();
    3938             configureColors();
    3939         }
    3940
    3941         boolean primaryMatches(ThumbnailLoggingEventWrapper wrapper) {
    3942             return !wrapper.loggingEventWrapper.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND);
    3943         }
    3944
    3945         boolean secondaryMatches(ThumbnailLoggingEventWrapper wrapper) {
    3946             return wrapper.loggingEventWrapper.isSearchMatch();
    3947         }
    3948
    3949         private void configureColors() {
    3950             secondaryList.clear();
    3951             primaryList.clear();
    3952
    3953             int i=0;
    3954             for (Iterator iter = tableModel.getFilteredEvents().iterator();iter.hasNext();) {
    3955                 LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper) iter.next();
    3956                 ThumbnailLoggingEventWrapper wrapper = new ThumbnailLoggingEventWrapper(i, loggingEventWrapper);
    3957                 if (secondaryMatches(wrapper)) {
    3958                     secondaryList.add(wrapper);
    3959                 }
    3960                 i++;
    3961                 //only add if there is a color defined
    3962                 if (primaryMatches(wrapper)) {
    3963                     primaryList.add(wrapper);
    3964                 }
    3965             }
    3966             revalidate();
    3967             repaint();
    3968         }
    3969
    3970         public void paintComponent(Graphics g) {
    3971             super.paintComponent(g);
    3972
    3973             int rowCount = table.getRowCount();
    3974             if (rowCount == 0) {
    3975                 return;
    3976             }
    3977             //use event pane height as reference height - max component height will be extended by event height if
    3978             // last row is rendered, so subtract here
    3979             int height = eventsPane.getHeight();
    3980             int maxHeight = Math.min(maxEventHeight, (height / rowCount));
    3981             int minHeight = Math.max(1, maxHeight);
    3982             int componentHeight = height - minHeight;
    3983             int eventHeight = minHeight;
    3984
    3985             //draw all non error/warning/marker events
    3986             for (Iterator iter = primaryList.iterator();iter.hasNext();) {
    3987                 ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper)iter.next();
    3988                 if (!wrapper.loggingEventWrapper.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND)) {
    3989                     if (wrapper.loggingEventWrapper.getLoggingEvent().getLevel().toInt() < Level.WARN.toInt() && wrapper.loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE) == null) {
    3990                         float ratio = (wrapper.rowNum / (float)rowCount);
    3991         //                System.out.println("error - ratio: " + ratio + ", component height: " + componentHeight);
    3992                         int verticalLocation = (int) (componentHeight * ratio);
    3993
    3994                         int startX = 1;
    3995                         int width = getWidth() - (startX * 2);
    3996
    3997                         drawEvent(wrapper.loggingEventWrapper.getColorRuleBackground(), verticalLocation, eventHeight, g, startX, width);
    3998         //                System.out.println("painting error - rownum: " + wrapper.rowNum + ", location: " + verticalLocation + ", height: " + eventHeight + ", component height: " + componentHeight + ", row count: " + rowCount);
    3999                     }
    4000                 }
    4001             }
    4002
    4003             //draw warnings, error, fatal & markers last (full width)
    4004             for (Iterator iter = primaryList.iterator();iter.hasNext();) {
    4005                 ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper)iter.next();
    4006                 if (!wrapper.loggingEventWrapper.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND)) {
    4007                     if (wrapper.loggingEventWrapper.getLoggingEvent().getLevel().toInt() >= Level.WARN.toInt() || wrapper.loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE) != null) {
    4008                         float ratio = (wrapper.rowNum / (float)rowCount);
    4009         //                System.out.println("error - ratio: " + ratio + ", component height: " + componentHeight);
    4010                         int verticalLocation = (int) (componentHeight * ratio);
    4011
    4012                         int startX = 1;
    4013                         int width = getWidth() - (startX * 2);
    4014                         //narrow the color a bit if level is less than warn
    4015                             //make warnings, errors a little taller
    4016
    4017                         eventHeight = Math.min(maxEventHeight, eventHeight + 3);
    4018 //                            eventHeight = maxEventHeight;
    4019
    4020                         drawEvent(wrapper.loggingEventWrapper.getColorRuleBackground(), (verticalLocation - eventHeight + 1), eventHeight, g, startX, width);
    4021     //                System.out.println("painting error - rownum: " + wrapper.rowNum + ", location: " + verticalLocation + ", height: " + eventHeight + ", component height: " + componentHeight + ", row count: " + rowCount);
    4022                     }
    4023                 }
    4024             }
    4025
    4026             for (Iterator iter = secondaryList.iterator();iter.hasNext();) {
    4027                 ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper)iter.next();
    4028                 float ratio = (wrapper.rowNum / (float)rowCount);
    4029 //                System.out.println("warning - ratio: " + ratio + ", component height: " + componentHeight);
    4030                 int verticalLocation = (int) (componentHeight * ratio);
    4031
    4032                 int startX = 1;
    4033                 int width = getWidth() - (startX * 2);
    4034                 width = (width / 2);
    4035
    4036                 //use black for search indicator in the 'gutter'
    4037                 drawEvent(Color.BLACK, verticalLocation, eventHeight, g, startX, width);
    4038 //                System.out.println("painting warning - rownum: " + wrapper.rowNum + ", location: " + verticalLocation + ", height: " + eventHeight + ", component height: " + componentHeight + ", row count: " + rowCount);
    4039             }
    4040         }
    4041     }
    4042
    4043     abstract class AbstractEventMatchThumbnail extends JPanel {
    4044         protected List primaryList = new ArrayList();
    4045         protected List secondaryList = new ArrayList();
    4046         protected final int maxEventHeight = 6;
    4047
    4048         AbstractEventMatchThumbnail() {
    4049             super();
    4050             addMouseMotionListener(new MouseMotionAdapter() {
    4051               public void mouseMoved(MouseEvent e) {
    4052                 if (preferenceModel.isThumbnailBarToolTips()) {
    4053                     int yPosition = e.getPoint().y;
    4054                     ThumbnailLoggingEventWrapper event = getEventWrapperAtPosition(yPosition);
    4055                     if (event != null) {
    4056                         setToolTipText(getToolTipTextForEvent(event.loggingEventWrapper));
    4057                     }
    4058                 } else {
    4059                     setToolTipText(null);
    4060                 }
    4061               }
    4062             });
    4063
    4064             addMouseListener(new MouseAdapter(){
    4065                 public void mouseClicked(MouseEvent e)
    4066                 {
    4067                     int yPosition = e.getPoint().y;
    4068                     ThumbnailLoggingEventWrapper event = getEventWrapperAtPosition(yPosition);
    4069 //                    System.out.println("rowToSelect: " + rowToSelect + ", closestRow: " + event.loggingEvent.getProperty("log4jid"));
    4070                     if (event != null) {
    4071                         int id = Integer.parseInt(event.loggingEventWrapper.getLoggingEvent().getProperty("log4jid"));
    4072                         setSelectedEvent(id);
    4073                     }
    4074                 }
    4075             });
    4076
    4077             tableModel.addTableModelListener(new TableModelListener(){
    4078                 public void tableChanged(TableModelEvent e) {
    4079                     int firstRow = e.getFirstRow();
    4080                     //lastRow may be Integer.MAX_VALUE..if so, set lastRow to rowcount - 1 (so rowcount may be negative here, which will bypass for loops below)
    4081                     int lastRow = Math.min(e.getLastRow(), table.getRowCount() - 1);
    4082                     //clear everything if we got an event w/-1 for first or last row
    4083                     if (firstRow < 0 || lastRow < 0) {
    4084                         primaryList.clear();
    4085                         secondaryList.clear();
    4086                     }
    4087
    4088 //                    System.out.println("lastRow: " + lastRow + ", first row: " + firstRow + ", original last row: " + e.getLastRow() + ", type: " + e.getType());
    4089
    4090                     List displayedEvents = tableModel.getFilteredEvents();
    4091                     if (e.getType() == TableModelEvent.INSERT) {
    4092 //                        System.out.println("insert - current warnings: " + warnings.size() + ", errors: " + errors.size() + ", first row: " + firstRow + ", last row: " + lastRow);
    4093                         for (int i=firstRow;i<lastRow;i++) {
    4094                             LoggingEventWrapper event = (LoggingEventWrapper)displayedEvents.get(i);
    4095                             ThumbnailLoggingEventWrapper wrapper = new ThumbnailLoggingEventWrapper(i, event);
    4096                             if (secondaryMatches(wrapper)) {
    4097                                 secondaryList.add(wrapper);
    4098 //                                System.out.println("added warning: " + i + " - " + event.getLevel());
    4099                             }
    4100                             if (primaryMatches(wrapper)) {
    4101                                 //add to this one
    4102                                 primaryList.add(wrapper);
    4103                             }
    4104 //                                System.out.println("added error: " + i + " - " + event.getLevel());
    4105                         }
    4106 //                        System.out.println("insert- new warnings: " + warnings + ", errors: " + errors);
    4107
    4108                         //run evaluation on rows & add to list
    4109                     } else if (e.getType() == TableModelEvent.DELETE) {
    4110                         //find each eventwrapper with an id in the deleted range and remove it...
    4111 //                        System.out.println("delete- current warnings: " + warnings.size() + ", errors: " + errors.size() + ", first row: " + firstRow + ", last row: " + lastRow + ", displayed event count: " + displayedEvents.size() );
    4112                         for (Iterator iter = secondaryList.iterator();iter.hasNext();) {
    4113                             ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper)iter.next();
    4114                             if ((wrapper.rowNum >= firstRow) && (wrapper.rowNum <= lastRow)) {
    4115 //                                System.out.println("deleting find: " + wrapper);
    4116                                 iter.remove();
    4117                             }
    4118                         }
    4119                         for (Iterator iter = primaryList.iterator();iter.hasNext();) {
    4120                             ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper)iter.next();
    4121                             if ((wrapper.rowNum >= firstRow) && (wrapper.rowNum <= lastRow)) {
    4122 //                                System.out.println("deleting error: " + wrapper);
    4123                                 iter.remove();
    4124                             }
    4125                         }
    4126 //                        System.out.println("delete- new warnings: " + warnings.size() + ", errors: " + errors.size());
    4127
    4128                         //remove any matching rows
    4129                     } else if (e.getType() == TableModelEvent.UPDATE) {
    4130 //                        System.out.println("update - about to delete old warnings in range: " + firstRow + " to " + lastRow + ", current warnings: " + warnings.size() + ", errors: " + errors.size());
    4131                         //find each eventwrapper with an id in the deleted range and remove it...
    4132                         for (Iterator iter = secondaryList.iterator();iter.hasNext();) {
    4133                             ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper)iter.next();
    4134                             if ((wrapper.rowNum >= firstRow) && (wrapper.rowNum <= lastRow)) {
    4135 //                                System.out.println("update - deleting warning: " + wrapper);
    4136                                 iter.remove();
    4137                             }
    4138                         }
    4139                         for (Iterator iter = primaryList.iterator();iter.hasNext();) {
    4140                             ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper)iter.next();
    4141                             if ((wrapper.rowNum >= firstRow) && (wrapper.rowNum <= lastRow)) {
    4142 //                                System.out.println("update - deleting error: " + wrapper);
    4143                                 iter.remove();
    4144                             }
    4145                         }
    4146 //                        System.out.println("update - after deleting old warnings in range: " + firstRow + " to " + lastRow + ", new warnings: " + warnings.size() + ", errors: " + errors.size());
    4147                         //NOTE: for update, we need to do i<= lastRow
    4148                         for (int i=firstRow;i<=lastRow;i++) {
    4149                             LoggingEventWrapper event = (LoggingEventWrapper)displayedEvents.get(i);
    4150                             ThumbnailLoggingEventWrapper wrapper = new ThumbnailLoggingEventWrapper(i, event);
    4151 //                                System.out.println("update - adding error: " + i + ", event: " + event.getMessage());
    4152                             //only add event to thumbnail if there is a color
    4153                             if (primaryMatches(wrapper)) {
    4154                                 //!wrapper.loggingEvent.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND)
    4155                                 primaryList.add(wrapper);
    4156                             } else {
    4157                                 primaryList.remove(wrapper);
    4158                             }
    4159
    4160                             if (secondaryMatches(wrapper)) {
    4161                                 //event.isSearchMatch())
    4162 //                                System.out.println("update - adding marker: " + i + ", event: " + event.getMessage());
    4163                                 secondaryList.add(wrapper);
    4164                             } else {
    4165                                 secondaryList.remove(wrapper);
    4166                             }
    4167                         }
    4168 //                        System.out.println("update - new warnings: " + warnings.size() + ", errors: " + errors.size());
    4169                     }
    4170                     revalidate();
    4171                     repaint();
    4172                     //run this in an invokeLater block to ensure this action is enqueued to the end of the EDT
    4173                     EventQueue.invokeLater(new Runnable() {
    4174                     public void run() {
    4175                       if (isScrollToBottom()) {
    4176                           scrollToBottom();
    4177                       }
    4178                     }});
    4179                 }
    4180             });
    4181         }
    4182
    4183         abstract boolean primaryMatches(ThumbnailLoggingEventWrapper wrapper);
    4184
    4185         abstract boolean secondaryMatches(ThumbnailLoggingEventWrapper wrapper);
    4186         /**
    4187          * Get event wrapper - may be null
    4188          * @param yPosition
    4189          * @return event wrapper or null
    4190          */
    4191         protected ThumbnailLoggingEventWrapper getEventWrapperAtPosition(int yPosition) {
    4192             int rowCount = table.getRowCount();
    4193
    4194             //'effective' height of this component is scrollpane height
    4195             int height = eventsPane.getHeight();
    4196
    4197             yPosition = Math.max(yPosition, 0);
    4198
    4199             //don't let clicklocation exceed height
    4200             if (yPosition >= height) {
    4201                 yPosition = height;
    4202             }
    4203
    4204     //                    System.out.println("clicked y pos: " + e.getPoint().y + ", relative: " + clickLocation);
    4205             float ratio = (float) yPosition / height;
    4206             int rowToSelect = Math.round(rowCount * ratio);
    4207     //                    System.out.println("rowCount: " + rowCount + ", height: " + height + ", clickLocation: " + clickLocation + ", ratio: " + ratio + ", rowToSelect: " + rowToSelect);
    4208             ThumbnailLoggingEventWrapper event = getClosestRow(rowToSelect);
    4209             return event;
    4210         }
    4211
    4212         private ThumbnailLoggingEventWrapper getClosestRow(int rowToSelect) {
    4213             ThumbnailLoggingEventWrapper closestRow = null;
    4214             int rowDelta = Integer.MAX_VALUE;
    4215             for (Iterator iter = secondaryList.iterator();iter.hasNext();) {
    4216                 ThumbnailLoggingEventWrapper event = (ThumbnailLoggingEventWrapper) iter.next();
    4217                 int newRowDelta = Math.abs(rowToSelect - event.rowNum);
    4218                 if (newRowDelta < rowDelta) {
    4219                     closestRow = event;
    4220                     rowDelta = newRowDelta;
    4221                 }
    4222             }
    4223             for (Iterator iter = primaryList.iterator();iter.hasNext();) {
    4224                 ThumbnailLoggingEventWrapper event = (ThumbnailLoggingEventWrapper) iter.next();
    4225                 int newRowDelta = Math.abs(rowToSelect - event.rowNum);
    4226                 if (newRowDelta < rowDelta) {
    4227                     closestRow = event;
    4228                     rowDelta = newRowDelta;
    4229                 }
    4230             }
    4231             return closestRow;
    4232         }
    4233
    4234         public Point getToolTipLocation(MouseEvent event) {
    4235             //shift tooltip down so the the pointer doesn't cover up events below the current mouse location
    4236             return new Point(event.getX(), event.getY() + 30);
    4237         }
    4238
    4239         protected void drawEvent(Color newColor, int verticalLocation, int eventHeight, Graphics g, int x, int width) {
    4240     //            System.out.println("painting: - color: " + newColor + ", verticalLocation: " + verticalLocation + ", eventHeight: " + eventHeight);
    4241             //center drawing at vertical location
    4242             int y = verticalLocation + (eventHeight / 2);
    4243             Color oldColor = g.getColor();
    4244             g.setColor(newColor);
    4245             g.fillRect(x, y, width, eventHeight);
    4246             if (eventHeight >= 3) {
    4247                 g.setColor(newColor.darker());
    4248                 g.drawRect(x, y, width, eventHeight);
    4249             }
    4250             g.setColor(oldColor);
    4251         }
    4252     }
    4253
    4254     class ThumbnailLoggingEventWrapper {
    4255         int rowNum;
    4256         LoggingEventWrapper loggingEventWrapper;
    4257         public ThumbnailLoggingEventWrapper(int rowNum, LoggingEventWrapper loggingEventWrapper) {
    4258             this.rowNum = rowNum;
    4259             this.loggingEventWrapper = loggingEventWrapper;
    4260         }
    4261
    4262         public String toString() {
    4263             return "event - rownum: " + rowNum + ", level: " + loggingEventWrapper.getLoggingEvent().getLevel();
    4264         }
    4265
    4266         public boolean equals(Object o) {
    4267             if (this == o) {
    4268                 return true;
    4269             }
    4270             if (o == null || getClass() != o.getClass()) {
    4271                 return false;
    4272             }
    4273
    4274             ThumbnailLoggingEventWrapper that = (ThumbnailLoggingEventWrapper) o;
    4275
    4276             if (loggingEventWrapper != null ? !loggingEventWrapper.equals(that.loggingEventWrapper) : that.loggingEventWrapper != null) {
    4277                 return false;
    4278             }
    4279
    4280             return true;
    4281         }
    4282
    4283         public int hashCode() {
    4284             return loggingEventWrapper != null ? loggingEventWrapper.hashCode() : 0;
    4285         }
    4286     }
    4287
    4288     class AutoFilterComboBox extends JComboBox {
    4289         private boolean bypassFiltering;
    4290         private List allEntries = new ArrayList();
    4291         private List displayedEntries = new ArrayList();
    4292         private AutoFilterComboBoxModel model = new AutoFilterComboBoxModel();
    4293         //editor component
    4294         private final JTextField textField = new JTextField();
    4295         private String lastTextToMatch;
    4296
    4297         public AutoFilterComboBox() {
    4298             textField.setPreferredSize(getPreferredSize());
    4299             setModel(model);
    4300             setEditor(new AutoFilterEditor());
    4301             ((JTextField)getEditor().getEditorComponent()).getDocument().addDocumentListener(new AutoFilterDocumentListener());
    4302             setEditable(true);
    4303             addPopupMenuListener(new PopupMenuListenerImpl());
    4304         }
    4305
    4306         public Vector getModelData() {
    4307             //reverse the model order, because it will be un-reversed when we reload it from saved settings
    4308             Vector vector = new Vector();
    4309             for (Iterator iter = allEntries.iterator();iter.hasNext();) {
    4310                 vector.insertElementAt(iter.next(), 0);
    4311             }
    4312             return vector;
    4313         }
    4314
    4315         private void refilter() {
    4316             //only refilter if we're not bypassing filtering AND the text has changed since the last call to refilter
    4317             String textToMatch = getEditor().getItem().toString();
    4318             if (bypassFiltering || (lastTextToMatch != null && lastTextToMatch.equals(textToMatch))) {
    4319                 return;
    4320             }
    4321             lastTextToMatch = textToMatch;
    4322             bypassFiltering = true;
    4323                 model.removeAllElements();
    4324                 List entriesCopy = new ArrayList(allEntries);
    4325                 for (Iterator iter = entriesCopy.iterator();iter.hasNext();) {
    4326                     String thisEntry = iter.next().toString();
    4327                     if (thisEntry.toLowerCase(Locale.ENGLISH).contains(textToMatch.toLowerCase())) {
    4328                         model.addElement(thisEntry);
    4329                     }
    4330                 }
    4331                 bypassFiltering = false;
    4332                 //TODO: on no-match, don't filter at all (show the popup?)
    4333                 if (displayedEntries.size() > 0 && !textToMatch.equals("")) {
    4334                     showPopup();
    4335                 } else {
    4336                     hidePopup();
    4337                 }
    4338         }
    4339
    4340         class AutoFilterEditor implements ComboBoxEditor {
    4341             public Component getEditorComponent() {
    4342                 return textField;
    4343             }
    4344
    4345             public void setItem(Object item) {
    4346                 if (bypassFiltering) {
    4347                     return;
    4348                 }
    4349                 bypassFiltering = true;
    4350                 if (item == null) {
    4351                     textField.setText("");
    4352                 } else {
    4353                     textField.setText(item.toString());
    4354                 }
    4355                 bypassFiltering = false;
    4356             }
    4357
    4358             public Object getItem() {
    4359                 return textField.getText();
    4360             }
    4361
    4362             public void selectAll() {
    4363                 textField.selectAll();
    4364             }
    4365
    4366             public void addActionListener(ActionListener listener) {
    4367                 textField.addActionListener(listener);
    4368             }
    4369
    4370             public void removeActionListener(ActionListener listener) {
    4371                 textField.removeActionListener(listener);
    4372             }
    4373         }
    4374
    4375         class AutoFilterDocumentListener implements DocumentListener {
    4376             public void insertUpdate(DocumentEvent e) {
    4377                 refilter();
    4378             }
    4379
    4380             public void removeUpdate(DocumentEvent e) {
    4381                 refilter();
    4382             }
    4383
    4384             public void changedUpdate(DocumentEvent e) {
    4385                 refilter();
    4386             }
    4387         }
    4388
    4389         class AutoFilterComboBoxModel extends AbstractListModel implements MutableComboBoxModel {
    4390             private Object selectedItem;
    4391
    4392             public void addElement(Object obj) {
    4393                 //assuming add is to displayed list...add to full list (only if not a dup)
    4394                 bypassFiltering = true;
    4395
    4396               boolean entryExists = !allEntries.contains(obj);
    4397               if (entryExists) {
    4398                   allEntries.add(obj);
    4399                 }
    4400                 displayedEntries.add(obj);
    4401                 if (!entryExists) {
    4402                   fireIntervalAdded(this, displayedEntries.size() - 1, displayedEntries.size());
    4403                 }
    4404                 bypassFiltering = false;
    4405             }
    4406
    4407             public void removeElement(Object obj) {
    4408                 int index = displayedEntries.indexOf(obj);
    4409                 if (index != -1) {
    4410                     removeElementAt(index);
    4411                 }
    4412             }
    4413
    4414             public void insertElementAt(Object obj, int index) {
    4415                 //assuming add is to displayed list...add to full list (only if not a dup)
    4416                 if (allEntries.contains(obj)) {
    4417                     return;
    4418                 }
    4419                 bypassFiltering = true;
    4420                 displayedEntries.add(index, obj);
    4421                 allEntries.add(index, obj);
    4422                 fireIntervalAdded(this, index, index);
    4423                 bypassFiltering = false;
    4424                 refilter();
    4425             }
    4426
    4427             public void removeElementAt(int index) {
    4428                 bypassFiltering = true;
    4429                 //assuming removal is from displayed list..remove from full list
    4430                 Object obj = displayedEntries.get(index);
    4431                 allEntries.remove(obj);
    4432                 displayedEntries.remove(obj);
    4433                 fireIntervalRemoved(this, index, index);
    4434                 bypassFiltering = false;
    4435                 refilter();
    4436             }
    4437
    4438             public void setSelectedItem(Object item) {
    4439                 if ((selectedItem != null && !selectedItem.equals(item)) || selectedItem == null && item != null) {
    4440                     selectedItem = item;
    4441                     fireContentsChanged(this, -1, -1);
    4442                 }
    4443             }
    4444
    4445             public Object getSelectedItem() {
    4446                 return selectedItem;
    4447             }
    4448
    4449             public int getSize() {
    4450                 return displayedEntries.size();
    4451             }
    4452
    4453             public Object getElementAt(int index) {
    4454                 if (index >= 0 && index < displayedEntries.size()) {
    4455                     return displayedEntries.get(index);
    4456                 }
    4457                 return null;
    4458             }
    4459
    4460             public void removeAllElements() {
    4461                 bypassFiltering = true;
    4462                 int displayedEntrySize = displayedEntries.size();
    4463                 if (displayedEntrySize > 0) {
    4464                   displayedEntries.clear();
    4465                   //if firecontentschaned is used, the combobox resizes..use fireintervalremoved instead, which doesn't do that..
    4466                   fireIntervalRemoved(this, 0, displayedEntrySize - 1);
    4467                 }
    4468                 bypassFiltering = false;
    4469             }
    4470
    4471             public void showAllElements() {
    4472               //first remove whatever is there and fire necessary events then add events
    4473                 removeAllElements();
    4474                 bypassFiltering = true;
    4475                 displayedEntries.addAll(allEntries);
    4476                 if (displayedEntries.size() > 0) {
    4477                   fireIntervalAdded(this, 0, displayedEntries.size() - 1);
    4478                 }
    4479                 bypassFiltering = false;
    4480             }
    4481         }
    4482
    4483         private class PopupMenuListenerImpl implements PopupMenuListener {
    4484             private boolean willBecomeVisible = false;
    4485
    4486             public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
    4487                 bypassFiltering = true;
    4488                 ((JComboBox)e.getSource()).setSelectedIndex(-1);
    4489                 bypassFiltering = false;
    4490                 if (!willBecomeVisible) {
    4491                     //we already have a match but we're showing the popup - unfilter
    4492                     if (displayedEntries.contains(textField.getText())) {
    4493                         model.showAllElements();
    4494                     }
    4495
    4496                     //workaround for bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4743225
    4497                     //the height of the popup after updating entries in this listener was not updated..
    4498                     JComboBox list = (JComboBox) e.getSource();
    4499                     willBecomeVisible = true; // the flag is needed to prevent a loop
    4500                     try {
    4501                         list.getUI().setPopupVisible(list, true);
    4502                     } finally {
    4503                         willBecomeVisible = false;
    4504                     }
    4505                 }
    4506             }
    4507
    4508             public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
    4509                 //no-op
    4510             }
    4511
    4512             public void popupMenuCanceled(PopupMenuEvent e) {
    4513                 //no-op
    4514             }
    4515         }
    4516     }
    4517
    4518   class ToggleToolTips extends JCheckBoxMenuItem {
    4519     public ToggleToolTips() {
    4520       super("Show ToolTips", new ImageIcon(ChainsawIcons.TOOL_TIP));
    4521   addActionListener(
    4522     new ActionListener() {
    4523       public void actionPerformed(ActionEvent evt) {
    4524         preferenceModel.setToolTips(isSelected());
    4525       }
    4526     });
    4527     }
    4528   }
    4529 }