public LogPanel()

in src/main/java/org/apache/log4j/chainsaw/LogPanel.java [191:1820]


    public LogPanel(final ChainsawStatusBar statusBar, 
            final String identifier,
            int cyclicBufferSize,
                    Map<String, RuleColorizer> allColorizers,
                    final ApplicationPreferenceModel applicationPreferenceModel,
                    RuleColorizer globalRuleColorizer) {
        this.identifier = identifier;
        this.statusBar = statusBar;
        this.applicationPreferenceModel = applicationPreferenceModel;
        this.logPanelPreferencesPanel = new LogPanelPreferencePanel(identifier);
        this.colorizer = globalRuleColorizer;
        this.m_globalColorizer = globalRuleColorizer;
        this.m_allColorizers = allColorizers;
        logger.debug("creating logpanel for {}", identifier);

        m_configuration = SettingsManager.getInstance().getCombinedSettingsForRecevierTab(identifier);
        AbstractConfiguration tabConfig = SettingsManager.getInstance().getSettingsForReceiverTab(identifier);

        setLayout(new BorderLayout());

        String prototypeValue = "1231231231231231231231";

        filterCombo = new AutoFilterComboBox();
        findCombo = new AutoFilterComboBox();

        filterCombo.setPrototypeDisplayValue(prototypeValue);
        buildCombo(filterCombo, true, findCombo.model);

        findCombo.setPrototypeDisplayValue(prototypeValue);
        buildCombo(findCombo, false, filterCombo.model);

        final Map<Object, String> columnNameKeywordMap = new HashMap<>();
        columnNameKeywordMap.put(ChainsawConstants.CLASS_COL_NAME, LoggingEventFieldResolver.CLASS_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.FILE_COL_NAME, LoggingEventFieldResolver.FILE_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.LEVEL_COL_NAME, LoggingEventFieldResolver.LEVEL_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.LINE_COL_NAME, LoggingEventFieldResolver.LINE_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.LOGGER_COL_NAME, LoggingEventFieldResolver.LOGGER_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.NDC_COL_NAME, LoggingEventFieldResolver.NDC_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.MESSAGE_COL_NAME, LoggingEventFieldResolver.MSG_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.THREAD_COL_NAME, LoggingEventFieldResolver.THREAD_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.THROWABLE_COL_NAME, LoggingEventFieldResolver.EXCEPTION_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.TIMESTAMP_COL_NAME, LoggingEventFieldResolver.TIMESTAMP_FIELD);
        columnNameKeywordMap.put(ChainsawConstants.ID_COL_NAME.toUpperCase(), LoggingEventFieldResolver.PROP_FIELD + Constants.LOG4J_ID_KEY);
        columnNameKeywordMap.put(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE.toUpperCase(), LoggingEventFieldResolver.PROP_FIELD + ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
        columnNameKeywordMap.put(ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE.toUpperCase(), LoggingEventFieldResolver.PROP_FIELD + ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE);

        logPanelPreferencesFrame.setTitle("'" + identifier + "' Log Panel Preferences");
        logPanelPreferencesFrame.setIconImage(
            ((ImageIcon) ChainsawIcons.ICON_PREFERENCES).getImage());
        logPanelPreferencesFrame.getContentPane().add(new JScrollPane(logPanelPreferencesPanel));

        logPanelPreferencesFrame.setSize(740, 520);

        logPanelPreferencesPanel.setOkCancelActionListener(
            e -> logPanelPreferencesFrame.setVisible(false));

        KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
        Action closeLogPanelPreferencesFrameAction = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                logPanelPreferencesFrame.setVisible(false);
            }
        };
        logPanelPreferencesFrame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "ESCAPE");
        logPanelPreferencesFrame.getRootPane().
            getActionMap().put("ESCAPE", closeLogPanelPreferencesFrameAction);


        setDetailPaneConversionPattern(
            DefaultLayoutFactory.getDefaultPatternLayout());
        detailLayout.setConversionPattern(
            DefaultLayoutFactory.getDefaultPatternLayout());

        undockedFrame = new JFrame(identifier);
        undockedFrame.setDefaultCloseOperation(
            WindowConstants.DO_NOTHING_ON_CLOSE);

        if (ChainsawIcons.UNDOCKED_ICON != null) {
            undockedFrame.setIconImage(
                new ImageIcon(ChainsawIcons.UNDOCKED_ICON).getImage());
        }

        externalPanel = new DockablePanel();
        externalPanel.setLayout(new BorderLayout());

        undockedFrame.addWindowListener(
            new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    dock();
                }
            });

        undockedToolbar = createDockwindowToolbar();
        externalPanel.add(undockedToolbar, BorderLayout.NORTH);
        undockedFrame.getContentPane().add(externalPanel);
        undockedFrame.setSize(new Dimension(1024, 768));
        undockedFrame.pack();

        tabConfig.addEventListener(ConfigurationEvent.SET_PROPERTY,
            evt -> {
                if( evt.getPropertyName().equals( "scrollToBottom" ) ){
                    boolean value = (Boolean) evt.getPropertyValue();
                    if (value) {
                        scrollToBottom();
                    }
                }
            });
        /*
         * Menus on which the preferencemodels rely
         */

        /**
         * Setup a popup menu triggered for Timestamp column to allow time stamp
         * format changes
         */
        final JPopupMenu dateFormatChangePopup = new JPopupMenu();
        final JRadioButtonMenuItem isoButton =
            new JRadioButtonMenuItem(
                new AbstractAction("Use ISO8601Format") {
                    public void actionPerformed(ActionEvent e) {
//                        preferenceModel.setDateFormatPattern("ISO8601");
                    }
                });
        final JRadioButtonMenuItem simpleTimeButton =
            new JRadioButtonMenuItem(
                new AbstractAction("Use simple time") {
                    public void actionPerformed(ActionEvent e) {
//                        preferenceModel.setDateFormatPattern("HH:mm:ss");
                    }
                });

        ButtonGroup dfBG = new ButtonGroup();
        dfBG.add(isoButton);
        dfBG.add(simpleTimeButton);
        simpleTimeButton.setSelected(true);
        dateFormatChangePopup.add(isoButton);
        dateFormatChangePopup.add(simpleTimeButton);

        final JCheckBoxMenuItem menuItemLoggerTree =
            new JCheckBoxMenuItem("Show Logger Tree");
        menuItemLoggerTree.addActionListener(
            e -> { m_configuration.setProperty("logpanel.logTreePanelVisible", 
                    menuItemLoggerTree.isSelected());});
        menuItemLoggerTree.setIcon(new ImageIcon(ChainsawIcons.WINDOW_ICON));

        final JCheckBoxMenuItem menuItemToggleDetails =
            new JCheckBoxMenuItem("Show Detail Pane");
        menuItemToggleDetails.addActionListener(
            e -> { m_configuration.setProperty("logpanel.detailColumnVisible",
                menuItemToggleDetails.isSelected());});

        menuItemToggleDetails.setIcon(new ImageIcon(ChainsawIcons.INFO));

        /*
         * add preferencemodel listeners
         */
//        preferenceModel.addPropertyChangeListener("levelIcons",
//            new PropertyChangeListener() {
//                public void propertyChange(PropertyChangeEvent evt) {
//                    boolean useIcons = (Boolean) evt.getNewValue();
//                    renderer.setLevelUseIcons(useIcons);
//                    table.tableChanged(new TableModelEvent(tableModel));
//                    searchRenderer.setLevelUseIcons(useIcons);
//                    searchTable.tableChanged(new TableModelEvent(searchModel));
//                }
//            });

        /*
         * add preferencemodel listeners
         */
        m_configuration.addEventListener(ConfigurationEvent.SET_PROPERTY,
            new org.apache.commons.configuration2.event.EventListener<ConfigurationEvent>(){
                @Override
                public void onEvent(ConfigurationEvent evt) {
                    if( evt.getPropertyName().equals( "logpanel.wrapMessage" ) ){
                        boolean wrap = (Boolean) evt.getPropertyValue();
                        renderer.setWrapMessage(wrap);
                        table.tableChanged(new TableModelEvent(tableModel));
                        searchRenderer.setWrapMessage(wrap);
                        searchTable.tableChanged(new TableModelEvent(searchModel));
                    }
                }

            } );

        m_configuration.addEventListener(ConfigurationEvent.SET_PROPERTY,
            new org.apache.commons.configuration2.event.EventListener<ConfigurationEvent>(){
                @Override
                public void onEvent(ConfigurationEvent evt) {
                    if( evt.getPropertyName().equals( "logpanel.searchResultsVisible" ) ){
                        boolean displaySearchResultsInDetailsIfAvailable = (Boolean) evt.getPropertyValue();
                        if (displaySearchResultsInDetailsIfAvailable) {
                            showSearchResults();
                        } else {
                            hideSearchResults();
                        }
                    }
                }

            } );

        m_configuration.addEventListener(ConfigurationEvent.SET_PROPERTY,
            new org.apache.commons.configuration2.event.EventListener<ConfigurationEvent>(){
                @Override
                public void onEvent(ConfigurationEvent evt) {
                    if( evt.getPropertyName().equals( "logpanel.highlightSearchMatchText" ) ){
                        boolean highlightText = (Boolean) evt.getPropertyValue();
                        renderer.setHighlightSearchMatchText(highlightText);
                        table.tableChanged(new TableModelEvent(tableModel));
                        searchRenderer.setHighlightSearchMatchText(highlightText);
                        searchTable.tableChanged(new TableModelEvent(searchModel));
                    }
                }

            } );

        tabConfig.addEventListener(ConfigurationEvent.SET_PROPERTY,
            new org.apache.commons.configuration2.event.EventListener<ConfigurationEvent>(){
                @Override
                public void onEvent(ConfigurationEvent evt) {
                    if( evt.getPropertyName().equals( "logpanel.detailColumnVisible" ) ){
                        boolean detailPaneVisible = (Boolean) evt.getPropertyValue();
                        if (detailPaneVisible) {
                            showDetailPane();
                        } else {
                            //don't hide the detail pane if search results are being displayed
                            if (!searchResultsDisplayed) {
                                hideDetailPane();
                            }
                        }
                        firePropertyChange("detailColumnVisible", !detailPaneVisible, detailPaneVisible);
                    }
                }

            } );

        tabConfig.addEventListener(ConfigurationEvent.SET_PROPERTY,
            new org.apache.commons.configuration2.event.EventListener<ConfigurationEvent>(){
                @Override
                public void onEvent(ConfigurationEvent evt) {
                    if( evt.getPropertyName().equals( "logpanel.logTreePanelVisible" ) ){
                        boolean newValue = (Boolean) evt.getPropertyValue();
                        if (newValue) {
                            showLogTreePanel();
                        } else {
                            hideLogTreePanel();
                        }
                        firePropertyChange("logTreePanelVisible", !newValue, newValue);
                    }
                }

            } );

        m_configuration.addEventListener(ConfigurationEvent.SET_PROPERTY,
            new org.apache.commons.configuration2.event.EventListener<ConfigurationEvent>(){
                @Override
                public void onEvent(ConfigurationEvent evt) {
                    if( evt.getPropertyName().equals( "logpanel.toolTips" ) ){
                        boolean toolTips = (Boolean) evt.getPropertyValue();
                        renderer.setToolTipsVisible(toolTips);
                        searchRenderer.setToolTipsVisible(toolTips);
                        searchToggleToolTips.setSelected(toolTips);
                        mainToggleToolTips.setSelected(toolTips);
                    }
                }

            } );

        preferenceModel.addPropertyChangeListener("visibleColumns",
            new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    //remove all columns and re-add visible
                    TableColumnModel columnModel = table.getColumnModel();
                    while (columnModel.getColumnCount() > 0) {
                        columnModel.removeColumn(columnModel.getColumn(0));
                    }
                    for (Object o1 : preferenceModel.getVisibleColumnOrder()) {
                        TableColumn c = (TableColumn) o1;
                        if (c.getHeaderValue().toString().equalsIgnoreCase(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE)) {
                            c.setCellEditor(markerCellEditor);
                        }
                        columnModel.addColumn(c);
                    }
                    TableColumnModel searchColumnModel = searchTable.getColumnModel();
                    while (searchColumnModel.getColumnCount() > 0) {
                        searchColumnModel.removeColumn(searchColumnModel.getColumn(0));
                    }
                    for (Object o : preferenceModel.getVisibleColumnOrder()) {
                        TableColumn c = (TableColumn) o;
                        searchColumnModel.addColumn(c);
                    }
                }
            });

        PropertyChangeListener datePrefsChangeListener =
            new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    LogPanelPreferenceModel model = (LogPanelPreferenceModel) evt.getSource();

                    isoButton.setSelected(model.isUseISO8601Format());
                    simpleTimeButton.setSelected(!model.isUseISO8601Format() && !model.isCustomDateFormat());

                    if (model.getTimeZone() != null) {
                        renderer.setTimeZone(model.getTimeZone());
                        searchRenderer.setTimeZone(model.getTimeZone());
                    }

                    if (model.isUseISO8601Format()) {
                        renderer.setDateFormatter(new SimpleDateFormat(Constants.ISO8601_PATTERN));
                        searchRenderer.setDateFormatter(new SimpleDateFormat(Constants.ISO8601_PATTERN));
                    } else {
                        try {
                            renderer.setDateFormatter(new SimpleDateFormat(model.getDateFormatPattern()));
                        } catch (IllegalArgumentException iae) {
                            model.setDefaultDatePatternFormat();
                            renderer.setDateFormatter(new SimpleDateFormat(Constants.ISO8601_PATTERN));
                        }
                        try {
                            searchRenderer.setDateFormatter(new SimpleDateFormat(model.getDateFormatPattern()));
                        } catch (IllegalArgumentException iae) {
                            model.setDefaultDatePatternFormat();
                            searchRenderer.setDateFormatter(new SimpleDateFormat(Constants.ISO8601_PATTERN));
                        }
                    }

                    table.tableChanged(new TableModelEvent(tableModel));
                    searchTable.tableChanged(new TableModelEvent(searchModel));
                }
            };

        preferenceModel.addPropertyChangeListener("dateFormatPattern", datePrefsChangeListener);
        preferenceModel.addPropertyChangeListener("dateFormatTimeZone", datePrefsChangeListener);

        m_configuration.addEventListener(ConfigurationEvent.SET_PROPERTY,
            evt -> {
                if( evt.getPropertyName().equals( "logpanel.clearTableExpression" ) ){
                    LogPanelPreferenceModel model = (LogPanelPreferenceModel) evt.getSource();
                    String expression = model.getClearTableExpression();
                    try {
                        clearTableExpressionRule = ExpressionRule.getRule(expression);
                        logger.info("clearTableExpressionRule set to: " + expression);
                    } catch (Exception e) {
                        logger.info("clearTableExpressionRule invalid - ignoring: " + expression);
                        clearTableExpressionRule = null;
                    }
                }
            });

        m_configuration.addEventListener(ConfigurationEvent.SET_PROPERTY,
            new org.apache.commons.configuration2.event.EventListener<ConfigurationEvent>(){
                @Override
                public void onEvent(ConfigurationEvent evt) {
                    if( evt.getPropertyName().equals( "logpanel.loggerPrecision" ) ){
                        LogPanelPreferenceModel model = (LogPanelPreferenceModel) evt.getSource();

                        renderer.setLoggerPrecision(model.getLoggerPrecision());
                        table.tableChanged(new TableModelEvent(tableModel));

                        searchRenderer.setLoggerPrecision(model.getLoggerPrecision());
                        searchTable.tableChanged(new TableModelEvent(searchModel));
                    }
                }
            });

        tabConfig.addEventListener(ConfigurationEvent.SET_PROPERTY,
            evt -> {
                if( evt.getPropertyName().equals("logpanel.logTreePanelVisible") ){
                    boolean value = (Boolean) evt.getPropertyValue();
                    menuItemLoggerTree.setSelected(value);
                }
            });

        tabConfig.addEventListener(ConfigurationEvent.SET_PROPERTY,
            evt -> {
                if( evt.getPropertyName().equals("logpanel.detailColumnVisible") ){
                    boolean value = (Boolean) evt.getPropertyValue();
                    menuItemToggleDetails.setSelected(value);

                }
            });

//        applicationPreferenceModel.addPropertyChangeListener("searchColor", new PropertyChangeListener() {
//            public void propertyChange(PropertyChangeEvent evt) {
//                if (table != null) {
//                    table.repaint();
//                }
//                if (searchTable != null) {
//                    searchTable.repaint();
//                }
//            }
//        });
//
//        applicationPreferenceModel.addPropertyChangeListener("alternatingColor", new PropertyChangeListener() {
//            public void propertyChange(PropertyChangeEvent evt) {
//                if (table != null) {
//                    table.repaint();
//                }
//                if (searchTable != null) {
//                    searchTable.repaint();
//                }
//            }
//        });

        /*
         *End of preferenceModel listeners
         */
        tableModel = new ChainsawCyclicBufferTableModel(cyclicBufferSize, colorizer, "main");
        table = new JSortTable(tableModel);

        markerCellEditor = new MarkerCellEditor();
        table.setName("main");
        table.setColumnSelectionAllowed(false);
        table.setRowSelectionAllowed(true);

        searchModel = new ChainsawCyclicBufferTableModel(cyclicBufferSize, colorizer, "search");
        searchTable = new JSortTable(searchModel);

        searchTable.setName("search");
        searchTable.setColumnSelectionAllowed(false);
        searchTable.setRowSelectionAllowed(true);

        //we've mapped f2, shift f2 and ctrl-f2 to marker-related actions, unmap them from the table
        table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("F2"), "none");
        table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, InputEvent.SHIFT_MASK), "none");
        table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "none");
        table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | InputEvent.SHIFT_MASK), "none");

        //we're also mapping ctrl-a to scroll-to-top, unmap from the table
        table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "none");

        searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("F2"), "none");
        searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, InputEvent.SHIFT_MASK), "none");
        searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "none");
        searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | InputEvent.SHIFT_MASK), "none");

        //we're also mapping ctrl-a to scroll-to-top, unmap from the table
        searchTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "none");

        //add a listener to update the 'refine focus'
        tableModel.addNewKeyListener(e -> columnNameKeywordMap.put(e.getKey(), "PROP." + e.getKey()));

        /*
         * Set the Display rule to use the mediator, the model will add itself as
         * a property change listener and update itself when the rule changes.
         */
        tableModel.setRuleMediator(tableRuleMediator);
        searchModel.setRuleMediator(searchRuleMediator);

        tableModel.addEventCountListener(
            (currentCount, totalCount) -> {
                if (LogPanel.this.isVisible()) {
                    statusBar.setSelectedLine(
                        table.getSelectedRow() + 1, currentCount, totalCount, getIdentifier());
                }
            });

        tableModel.addEventCountListener(
            new EventCountListener() {
                final NumberFormat formatter = NumberFormat.getPercentInstance();
                boolean warning75 = false;
                boolean warning100 = false;

                public void eventCountChanged(int currentCount, int totalCount) {
                    if (preferenceModel.isCyclic()) {
                        double percent =
                            ((double) totalCount) / tableModel.getMaxSize();
                        String msg;
                        boolean wasWarning = warning75 || warning100;
                        if ((percent > 0.75) && (percent < 1.0) && !warning75) {
                            msg =
                                "Warning :: " + formatter.format(percent) + " of the '"
                                    + getIdentifier() + "' buffer has been used";
                            warning75 = true;
                        } else if ((percent >= 1.0) && !warning100) {
                            msg =
                                "Warning :: " + formatter.format(percent) + " of the '"
                                    + getIdentifier()
                                    + "' buffer has been used.  Older events are being discarded.";
                            warning100 = true;
                        } else {
                            //clear msg
                            msg = "";
                            warning75 = false;
                            warning100 = false;
                        }

                        if (msg != null && wasWarning) {
                            statusBar.setMessage(msg);
                        }
                    }
                }
            });

        /*
         * Logger tree panel
         *
         */
        LogPanelLoggerTreeModel logTreeModel = new LogPanelLoggerTreeModel();
        logTreePanel = new LoggerNameTreePanel(logTreeModel, tabConfig, this, colorizer, filterModel);
        logTreePanel.getLoggerVisibilityRule().addPropertyChangeListener(evt -> {
            if (evt.getPropertyName().equals("searchExpression")) {
                findCombo.setSelectedItem(evt.getNewValue().toString());
                findNext();
            }
        });
        logTreePanel.addComponentListener(new ComponentListener() {
            @Override
            public void componentResized(ComponentEvent ce) {
                Dimension dim = ce.getComponent().getSize();
                tabConfig.setProperty("logpanel.treeDividerLocation", dim.width);
            }

            @Override
            public void componentMoved(ComponentEvent ce) {}
            @Override
            public void componentShown(ComponentEvent ce) {}
            @Override
            public void componentHidden(ComponentEvent ce) {}
        });

        tableModel.addLoggerNameListener(logTreeModel);
        tableModel.addLoggerNameListener(logTreePanel);

        /**
         * Set the LoggerRule to be the LoggerTreePanel, as this visual component
         * is a rule itself, and the RuleMediator will automatically listen when
         * it's rule state changes.
         */
        tableRuleMediator.setLoggerRule(logTreePanel.getLoggerVisibilityRule());
        searchRuleMediator.setLoggerRule(logTreePanel.getLoggerVisibilityRule());

        colorizer.setLoggerRule(logTreePanel.getLoggerColorRule());

        /*
         * Color rule frame and panel
         */
        colorFrame.setTitle("'" + identifier + "' color settings");
        colorFrame.setIconImage(
            ((ImageIcon) ChainsawIcons.ICON_PREFERENCES).getImage());

        allColorizers.put(identifier, colorizer);
        colorPanel = new ColorPanel(m_globalColorizer, filterModel, allColorizers, this);

        colorFrame.getContentPane().add(colorPanel);

        Action closeColorPanelAction = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                colorPanel.hidePanel();
            }
        };
        colorFrame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "ESCAPE");
        colorFrame.getRootPane().
            getActionMap().put("ESCAPE", closeColorPanelAction);

        colorPanel.setCloseActionListener(
            e -> colorFrame.setVisible(false));

        colorizer.addPropertyChangeListener(
            "colorrule",
            new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    for (Object o : tableModel.getAllEvents()) {
                        LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper) o;
                        loggingEventWrapper.updateColorRuleColors(colorizer.getBackgroundColor(loggingEventWrapper.getLoggingEvent()), colorizer.getForegroundColor(loggingEventWrapper.getLoggingEvent()));
                    }
//          no need to update searchmodel events since tablemodel and searchmodel share all events, and color rules aren't different between the two
//          if that changes, un-do the color syncing in loggingeventwrapper & re-enable this code
//
//          for (Iterator iter = searchModel.getAllEvents().iterator();iter.hasNext();) {
//             LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper)iter.next();
//             loggingEventWrapper.updateColorRuleColors(colorizer.getBackgroundColor(loggingEventWrapper.getLoggingEvent()), colorizer.getForegroundColor(loggingEventWrapper.getLoggingEvent()));
//           }
                    colorizedEventAndSearchMatchThumbnail.configureColors();
                    lowerPanel.revalidate();
                    lowerPanel.repaint();

                    searchTable.revalidate();
                    searchTable.repaint();
                }
            });

        /*
         * Table definition.  Actual construction is above (next to tablemodel)
         */
        table.setRowHeight(ChainsawConstants.DEFAULT_ROW_HEIGHT);
        table.setRowMargin(0);
        table.getColumnModel().setColumnMargin(0);
        table.setShowGrid(false);
        table.getColumnModel().addColumnModelListener(new ChainsawTableColumnModelListener(table));
        table.setAutoCreateColumnsFromModel(false);
        table.addMouseMotionListener(new TableColumnDetailMouseListener(table, tableModel));
        table.addMouseListener(new TableMarkerListener(table, tableModel, searchModel));
        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

        searchTable.setRowHeight(ChainsawConstants.DEFAULT_ROW_HEIGHT);
        searchTable.setRowMargin(0);
        searchTable.getColumnModel().setColumnMargin(0);
        searchTable.setShowGrid(false);
        searchTable.getColumnModel().addColumnModelListener(new ChainsawTableColumnModelListener(searchTable));
        searchTable.setAutoCreateColumnsFromModel(false);
        searchTable.addMouseMotionListener(new TableColumnDetailMouseListener(searchTable, searchModel));
        searchTable.addMouseListener(new TableMarkerListener(searchTable, searchModel, tableModel));
        searchTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);


        //set valueisadjusting if holding down a key - don't process setdetail events
        table.addKeyListener(
            new KeyListener() {
                public void keyTyped(KeyEvent e) {
                }

                public void keyPressed(KeyEvent e) {
                    synchronized (detail) {
                        table.getSelectionModel().setValueIsAdjusting(true);
                        detail.notify();
                    }
                }

                public void keyReleased(KeyEvent e) {
                    synchronized (detail) {
                        table.getSelectionModel().setValueIsAdjusting(false);
                        detail.notify();
                    }
                }
            });

        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        searchTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        table.getSelectionModel().addListSelectionListener(evt -> {
                if (((evt.getFirstIndex() == evt.getLastIndex())
                    && (evt.getFirstIndex() > 0) && previousLastIndex != -1) || (evt.getValueIsAdjusting())) {
                    return;
                }
                boolean lastIndexOnLastRow = (evt.getLastIndex() == (table.getRowCount() - 1));
                boolean lastIndexSame = (previousLastIndex == evt.getLastIndex());

                /*
                 * when scroll-to-bottom is active, here is what events look like:
                 * rowcount-1: 227, last: 227, previous last: 191..first: 191
                 *
                 * when the user has unselected the bottom row, here is what the events look like:
                 * rowcount-1: 227, last: 227, previous last: 227..first: 222
                 *
                 * note: previouslast is set after it is evaluated in the bypass scroll check
                 */
                //System.out.println("rowcount: " + (table.getRowCount() - 1) + ", last: " + evt.getLastIndex() +", previous last: " + previousLastIndex + "..first: " + evt.getFirstIndex() + ", isadjusting: " + evt.getValueIsAdjusting());

                boolean disableScrollToBottom = (lastIndexOnLastRow && lastIndexSame && previousLastIndex != evt.getFirstIndex());
                if (disableScrollToBottom && isScrollToBottom() && table.getRowCount() > 0) {
                    preferenceModel.setScrollToBottom(false);
                }
                previousLastIndex = evt.getLastIndex();
            }
        );

        table.getSelectionModel().addListSelectionListener(
            new ListSelectionListener() {
                public void valueChanged(ListSelectionEvent evt) {
                    if (((evt.getFirstIndex() == evt.getLastIndex())
                        && (evt.getFirstIndex() > 0) && previousLastIndex != -1) || (evt.getValueIsAdjusting())) {
                        return;
                    }

                    final ListSelectionModel lsm = (ListSelectionModel) evt.getSource();

                    if (lsm.isSelectionEmpty()) {
                        if (isVisible()) {
                            statusBar.setNothingSelected();
                        }

                        if (detail.getDocument().getDefaultRootElement() != null) {
                            detailPaneUpdater.setSelectedRow(-1);
                        }
                    } else {
                        if (table.getSelectedRow() > -1) {
                            int selectedRow = table.getSelectedRow();

                            if (isVisible()) {
                                updateStatusBar();
                            }

                            try {
                                if (tableModel.getRowCount() >= selectedRow) {
                                    detailPaneUpdater.setSelectedRow(table.getSelectedRow());
                                } else {
                                    detailPaneUpdater.setSelectedRow(-1);
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                                detailPaneUpdater.setSelectedRow(-1);
                            }
                        }
                    }
                }
            });

        renderer = new TableColorizingRenderer(colorizer, applicationPreferenceModel, tableModel, preferenceModel, true);
        renderer.setToolTipsVisible(preferenceModel.isToolTips());

        table.setDefaultRenderer(Object.class, renderer);

        searchRenderer = new TableColorizingRenderer(colorizer, applicationPreferenceModel, searchModel, preferenceModel, false);
        searchRenderer.setToolTipsVisible(preferenceModel.isToolTips());

        searchTable.setDefaultRenderer(Object.class, searchRenderer);

        /*
         * Throwable popup
         */
        table.addMouseListener(new ThrowableDisplayMouseAdapter(table, tableModel));
        searchTable.addMouseListener(new ThrowableDisplayMouseAdapter(searchTable, searchModel));

        //select a row in the main table when a row in the search table is selected
        searchTable.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                LoggingEventWrapper loggingEventWrapper = searchModel.getRow(searchTable.getSelectedRow());
                if (loggingEventWrapper != null) {
                    int id = Integer.parseInt(loggingEventWrapper.getLoggingEvent().getProperty("log4jid"));
                    //preserve the table's viewble column
                    setSelectedEvent(id);
                }
            }
        });

        /*
         * We listen for new Key's coming in so we can get them automatically
         * added as columns
         */
        tableModel.addNewKeyListener(
            e -> SwingHelper.invokeOnEDT(() -> {
// 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
//this may throw an illegalargexception - ignore it because we need to add only if not already added
                //if the column is already added, don't add again

                try {
                    if (table.getColumn(e.getKey()) != null) {
                        return;
                    }
//no need to check search table - we use the same columns
                } catch (IllegalArgumentException iae) {
                }
                TableColumn col = new TableColumn(e.getNewModelIndex());
                col.setHeaderValue(e.getKey());

                if (preferenceModel.addColumn(col)) {
                    if (preferenceModel.isColumnVisible(col) || !applicationPreferenceModel.isDefaultColumnsSet() || applicationPreferenceModel.isDefaultColumnsSet() &&
                        applicationPreferenceModel.getDefaultColumnNames().contains(col.getHeaderValue())) {
                        table.addColumn(col);
                        searchTable.addColumn(col);
                        preferenceModel.setColumnVisible(e.getKey().toString(), true);
                    }
                }
            }));

        //if the table is refiltered, try to reselect the last selected row
        //refilter with a newValue of TRUE means refiltering is about to begin
        //refilter with a newValue of FALSE means refiltering is complete
        //assuming notification is called on the EDT so we can in the current EDT call update the scroll & selection
        tableModel.addPropertyChangeListener("refilter", new PropertyChangeListener() {
            private LoggingEventWrapper currentEvent;

            public void propertyChange(PropertyChangeEvent evt) {
                //if new value is true, filtering is about to begin
                //if new value is false, filtering is complete
                if (evt.getNewValue().equals(Boolean.TRUE)) {
                    int currentRow = table.getSelectedRow();
                    if (currentRow > -1) {
                        currentEvent = tableModel.getRow(currentRow);
                    }
                } else {
                    if (currentEvent != null) {
                        table.scrollToRow(tableModel.getRowIndex(currentEvent));
                    }
                }
            }
        });

        table.getTableHeader().addMouseListener(
            new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    checkEvent(e);
                }

                public void mousePressed(MouseEvent e) {
                    checkEvent(e);
                }

                public void mouseReleased(MouseEvent e) {
                    checkEvent(e);
                }

                private void checkEvent(MouseEvent e) {
                    if (e.isPopupTrigger()) {
                        TableColumnModel colModel = table.getColumnModel();
                        int index = colModel.getColumnIndexAtX(e.getX());
                        int modelIndex = colModel.getColumn(index).getModelIndex();

                        if ((modelIndex + 1) == ChainsawColumns.INDEX_TIMESTAMP_COL_NAME) {
                            dateFormatChangePopup.show(e.getComponent(), e.getX(), e.getY());
                        }
                    }
                }
            });

        /*
         * Upper panel definition
         */
        JPanel upperPanel = new JPanel();
        upperPanel.setLayout(new BoxLayout(upperPanel, BoxLayout.X_AXIS));
        upperPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 0));

        final JLabel filterLabel = new JLabel("Refine focus on: ");
        filterLabel.setFont(filterLabel.getFont().deriveFont(Font.BOLD));

        upperPanel.add(filterLabel);
        upperPanel.add(Box.createHorizontalStrut(3));
        upperPanel.add(filterCombo);
        upperPanel.add(Box.createHorizontalStrut(3));

        final JTextField filterText = (JTextField) filterCombo.getEditor().getEditorComponent();
        final JTextField findText = (JTextField) findCombo.getEditor().getEditorComponent();


        //Adding a button to clear filter expressions which are currently remembered by Chainsaw...
        final JButton removeFilterButton = new JButton(" Remove ");

        removeFilterButton.setToolTipText("Click here to remove the selected expression from the list");
        removeFilterButton.addActionListener(
            new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    Object selectedItem = filterCombo.getSelectedItem();
                    if (e.getSource() == removeFilterButton && selectedItem != null && !selectedItem.toString().trim().equals("")) {
                        //don't just remove the entry from the store, clear the field
                        int index = filterCombo.getSelectedIndex();
                        filterText.setText(null);
                        filterCombo.setSelectedIndex(-1);
                        filterCombo.removeItemAt(index);
                        if (!(findCombo.getSelectedItem() != null && findCombo.getSelectedItem().equals(selectedItem))) {
                            //now remove the entry from the other model
                            ((AutoFilterComboBox.AutoFilterComboBoxModel) findCombo.getModel()).removeElement(selectedItem);
                        }
                    }
                }
            }
        );
        upperPanel.add(removeFilterButton);
        //add some space between refine focus and search sections of the panel
        upperPanel.add(Box.createHorizontalStrut(25));

        final JLabel findLabel = new JLabel("Find: ");
        findLabel.setFont(filterLabel.getFont().deriveFont(Font.BOLD));

        upperPanel.add(findLabel);
        upperPanel.add(Box.createHorizontalStrut(3));

        upperPanel.add(findCombo);
        upperPanel.add(Box.createHorizontalStrut(3));

        Action findNextAction = getFindNextAction();
        Action findPreviousAction = getFindPreviousAction();
        //add up & down search
        JButton findNextButton = new SmallButton(findNextAction);
        findNextButton.setText("");
        findNextButton.getActionMap().put(
            findNextAction.getValue(Action.NAME), findNextAction);
        findNextButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
            (KeyStroke) findNextAction.getValue(Action.ACCELERATOR_KEY),
            findNextAction.getValue(Action.NAME));

        JButton findPreviousButton = new SmallButton(findPreviousAction);
        findPreviousButton.setText("");
        findPreviousButton.getActionMap().put(
            findPreviousAction.getValue(Action.NAME), findPreviousAction);
        findPreviousButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
            (KeyStroke) findPreviousAction.getValue(Action.ACCELERATOR_KEY),
            findPreviousAction.getValue(Action.NAME));

        upperPanel.add(findNextButton);

        upperPanel.add(findPreviousButton);
        upperPanel.add(Box.createHorizontalStrut(3));

        //Adding a button to clear filter expressions which are currently remembered by Chainsaw...
        final JButton removeFindButton = new JButton(" Remove ");
        removeFindButton.setToolTipText("Click here to remove the selected expression from the list");
        removeFindButton.addActionListener(
            new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    Object selectedItem = findCombo.getSelectedItem();
                    if (e.getSource() == removeFindButton && selectedItem != null && !selectedItem.toString().trim().equals("")) {
                        //don't just remove the entry from the store, clear the field
                        int index = findCombo.getSelectedIndex();
                        findText.setText(null);
                        findCombo.setSelectedIndex(-1);
                        findCombo.removeItemAt(index);
                        if (!(filterCombo.getSelectedItem() != null && filterCombo.getSelectedItem().equals(selectedItem))) {
                            //now remove the entry from the other model if it wasn't selected
                            ((AutoFilterComboBox.AutoFilterComboBoxModel) filterCombo.getModel()).removeElement(selectedItem);
                        }
                    }
                }
            }
        );
        upperPanel.add(removeFindButton);

        //define search and refine focus selection and clear actions
        Action findFocusAction = new AbstractAction() {
            public void actionPerformed(ActionEvent actionEvent) {
                findCombo.requestFocus();
            }
        };

        Action filterFocusAction = new AbstractAction() {
            public void actionPerformed(ActionEvent actionEvent) {
                filterCombo.requestFocus();
            }
        };

        Action findClearAction = new AbstractAction() {
            public void actionPerformed(ActionEvent actionEvent) {
                findCombo.setSelectedIndex(-1);
                findNext();
            }
        };

        Action filterClearAction = new AbstractAction() {
            public void actionPerformed(ActionEvent actionEvent) {
                setRefineFocusText("");
                filterCombo.refilter();
            }
        };

        //now add them to the action and input maps for the logpanel
        KeyStroke ksFindFocus =
            KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
        KeyStroke ksFilterFocus =
            KeyStroke.getKeyStroke(KeyEvent.VK_R, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
        KeyStroke ksFindClear =
            KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.SHIFT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
        KeyStroke ksFilterClear =
            KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.SHIFT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());

        getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksFindFocus, "FindFocus");
        getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksFilterFocus, "FilterFocus");
        getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksFindClear, "FindClear");
        getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksFilterClear, "FilterClear");

        getActionMap().put("FindFocus", findFocusAction);
        getActionMap().put("FilterFocus", filterFocusAction);
        getActionMap().put("FindClear", findClearAction);
        getActionMap().put("FilterClear", filterClearAction);

        /*
         * Detail pane definition
         */
        detail = new JEditorPane(ChainsawConstants.DETAIL_CONTENT_TYPE, "");
        detail.setEditable(false);

        detailPaneUpdater = new DetailPaneUpdater();

        //if the panel gets focus, update the detail pane
        addFocusListener(new FocusListener() {

            public void focusGained(FocusEvent e) {
                detailPaneUpdater.updateDetailPane();
            }

            public void focusLost(FocusEvent e) {

            }
        });
        findMarkerRule = ExpressionRule.getRule("prop." + ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE + " exists");

        tableModel.addTableModelListener(e -> {
            int currentRow = table.getSelectedRow();
            if (e.getFirstRow() <= currentRow && e.getLastRow() >= currentRow) {
//current row has changed - update
                detailPaneUpdater.setAndUpdateSelectedRow(table.getSelectedRow());
            }
        });
        addPropertyChangeListener("detailPaneConversionPattern", detailPaneUpdater);
        addPropertyChangeListener("detailPaneDatetimeFormat", detailPaneUpdater);

        searchPane = new JScrollPane(searchTable);
        searchPane.getVerticalScrollBar().setUnitIncrement(ChainsawConstants.DEFAULT_ROW_HEIGHT * 2);
        searchPane.setPreferredSize(new Dimension(900, 50));

        //default detail panel to contain detail panel - if searchResultsVisible is true, when a search if triggered, update detail pane to contain search results
        detailPane = new JScrollPane(detail);
        detailPane.setPreferredSize(new Dimension(900, 50));

        detailPanel.add(detailPane, BorderLayout.CENTER);

        JPanel eventsAndStatusPanel = new JPanel(new BorderLayout());

        eventsPane = new JScrollPane(table);
        eventsPane.getVerticalScrollBar().setUnitIncrement(ChainsawConstants.DEFAULT_ROW_HEIGHT * 2);

        eventsAndStatusPanel.add(eventsPane, BorderLayout.CENTER);

        Integer scrollBarWidth = (Integer) UIManager.get("ScrollBar.width");

        JPanel rightPanel = new JPanel();
        rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));

        JPanel rightThumbNailPanel = new JPanel();
        rightThumbNailPanel.setLayout(new BoxLayout(rightThumbNailPanel, BoxLayout.Y_AXIS));
        rightThumbNailPanel.add(Box.createVerticalStrut(scrollBarWidth));
        colorizedEventAndSearchMatchThumbnail = new ColorizedEventAndSearchMatchThumbnail();
        rightThumbNailPanel.add(colorizedEventAndSearchMatchThumbnail);
        rightThumbNailPanel.add(Box.createVerticalStrut(scrollBarWidth));
        rightPanel.add(rightThumbNailPanel);
        //set thumbnail width to be a bit narrower than scrollbar width
        if (scrollBarWidth != null) {
            rightThumbNailPanel.setPreferredSize(new Dimension(scrollBarWidth - 4, -1));
        }
        eventsAndStatusPanel.add(rightPanel, BorderLayout.EAST);

        JPanel leftPanel = new JPanel();
        leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS));

        JPanel leftThumbNailPanel = new JPanel();
        leftThumbNailPanel.setLayout(new BoxLayout(leftThumbNailPanel, BoxLayout.Y_AXIS));
        leftThumbNailPanel.add(Box.createVerticalStrut(scrollBarWidth));
        eventTimeDeltaMatchThumbnail = new EventTimeDeltaMatchThumbnail();
        leftThumbNailPanel.add(eventTimeDeltaMatchThumbnail);
        leftThumbNailPanel.add(Box.createVerticalStrut(scrollBarWidth));
        leftPanel.add(leftThumbNailPanel);

        //set thumbnail width to be a bit narrower than scrollbar width
        if (scrollBarWidth != null) {
            leftThumbNailPanel.setPreferredSize(new Dimension(scrollBarWidth - 4, -1));
        }
        eventsAndStatusPanel.add(leftPanel, BorderLayout.WEST);

        final JPanel statusLabelPanel = new JPanel();
        statusLabelPanel.setLayout(new BorderLayout());

        statusLabelPanel.add(upperPanel, BorderLayout.CENTER);
        eventsAndStatusPanel.add(statusLabelPanel, BorderLayout.NORTH);

        /*
         * Detail panel layout editor
         */
        detailToolbar = new JToolBar(SwingConstants.HORIZONTAL);
        detailToolbar.setFloatable(false);

        final LayoutEditorPane layoutEditorPane = new LayoutEditorPane();
        final JDialog layoutEditorDialog =
            new JDialog((JFrame) null, "Pattern Editor");
        layoutEditorDialog.getContentPane().add(layoutEditorPane);
        layoutEditorDialog.setSize(640, 480);

        layoutEditorPane.addCancelActionListener(
            e -> layoutEditorDialog.setVisible(false));

        layoutEditorPane.addOkActionListener(
            e -> {
                setDetailPaneConversionPattern(
                    layoutEditorPane.getConversionPattern());
                setDetailPaneDatetimeFormat(layoutEditorPane.getDatetimeFormatter());
                layoutEditorDialog.setVisible(false);
            });

        Action copyToRefineFocusAction = new AbstractAction("Set 'refine focus' field") {
            public void actionPerformed(ActionEvent e) {
                String selectedText = detail.getSelectedText();
                if (selectedText == null || selectedText.equals("")) {
                    //no-op empty searches
                    return;
                }
                filterText.setText("msg ~= '" + selectedText + "'");
            }
        };

        Action copyToSearchAction = new AbstractAction("Find next") {
            public void actionPerformed(ActionEvent e) {
                String selectedText = detail.getSelectedText();
                if (selectedText == null || selectedText.equals("")) {
                    //no-op empty searches
                    return;
                }
                findCombo.setSelectedItem("msg ~= '" + selectedText + "'");
                findNext();
            }
        };

        Action editDetailAction =
            new AbstractAction(
                "Edit...", new ImageIcon(ChainsawIcons.ICON_EDIT_RECEIVER)) {
                public void actionPerformed(ActionEvent e) {
                    layoutEditorPane.setConversionPattern(
                        getDetailPaneConversionPattern());
                    layoutEditorPane.setDatetimeFormatter(getDetailPaneDatetimeFormat());

                    Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
                    Point p =
                        new Point(
                            ((int) ((size.getWidth() / 2)
                                - (layoutEditorDialog.getSize().getWidth() / 2))),
                            ((int) ((size.getHeight() / 2)
                                - (layoutEditorDialog.getSize().getHeight() / 2))));
                    layoutEditorDialog.setLocation(p);

                    layoutEditorDialog.setVisible(true);
                }
            };

        editDetailAction.putValue(
            Action.SHORT_DESCRIPTION,
            "opens a Dialog window to Edit the Pattern Layout text");

        final SmallButton editDetailButton = new SmallButton(editDetailAction);
        editDetailButton.setText(null);
        detailToolbar.add(Box.createHorizontalGlue());
        detailToolbar.add(editDetailButton);
        detailToolbar.addSeparator();
        detailToolbar.add(Box.createHorizontalStrut(5));

        Action closeDetailAction =
            new AbstractAction(null, LineIconFactory.createCloseIcon()) {
                public void actionPerformed(ActionEvent arg0) {
                    tabConfig.setProperty("logpanel.detailColumnVisible", false);
                }
            };

        closeDetailAction.putValue(
            Action.SHORT_DESCRIPTION, "Hides the Detail Panel");

        SmallButton closeDetailButton = new SmallButton(closeDetailAction);
        detailToolbar.add(closeDetailButton);

        detailPanel.add(detailToolbar, BorderLayout.NORTH);

        lowerPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT, eventsAndStatusPanel, detailPanel);

        dividerSize = lowerPanel.getDividerSize();
        lowerPanel.setDividerLocation(-1);

        lowerPanel.setResizeWeight(1.0);
        lowerPanel.setBorder(null);
        lowerPanel.setContinuousLayout(true);

        JPopupMenu editDetailPopupMenu = new JPopupMenu();

        editDetailPopupMenu.add(copyToRefineFocusAction);
        editDetailPopupMenu.add(copyToSearchAction);
        editDetailPopupMenu.addSeparator();

        editDetailPopupMenu.add(editDetailAction);
        editDetailPopupMenu.addSeparator();

        final ButtonGroup layoutGroup = new ButtonGroup();

        JRadioButtonMenuItem defaultLayoutRadio =
            new JRadioButtonMenuItem(
                new AbstractAction("Set to Default Layout") {
                    public void actionPerformed(ActionEvent e) {
                        setDetailPaneConversionPattern(
                            DefaultLayoutFactory.getDefaultPatternLayout());
                    }
                });

        JRadioButtonMenuItem fullLayoutRadio =
            new JRadioButtonMenuItem(
                new AbstractAction("Set to Full Layout") {
                    public void actionPerformed(ActionEvent e) {
                        setDetailPaneConversionPattern(
                            DefaultLayoutFactory.getFullPatternLayout());
                    }
                });

        editDetailPopupMenu.add(defaultLayoutRadio);
        editDetailPopupMenu.add(fullLayoutRadio);

        layoutGroup.add(defaultLayoutRadio);
        layoutGroup.add(fullLayoutRadio);
        defaultLayoutRadio.setSelected(true);

        JRadioButtonMenuItem tccLayoutRadio =
            new JRadioButtonMenuItem(
                new AbstractAction("Set to TCCLayout") {
                    public void actionPerformed(ActionEvent e) {
//                        setDetailPaneConversionPattern(
//                            PatternLayout.TTCC_CONVERSION_PATTERN);
                    }
                });
        editDetailPopupMenu.add(tccLayoutRadio);
        layoutGroup.add(tccLayoutRadio);

        PopupListener editDetailPopupListener =
            new PopupListener(editDetailPopupMenu);
        detail.addMouseListener(editDetailPopupListener);

        /*
         * Logger tree splitpane definition
         */
        nameTreeAndMainPanelSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, logTreePanel, lowerPanel);
        nameTreeAndMainPanelSplit.setDividerLocation(-1);

        add(nameTreeAndMainPanelSplit, BorderLayout.CENTER);

        if (isLogTreeVisible()) {
            showLogTreePanel();
        } else {
            hideLogTreePanel();
        }

        /*
         * Other menu items
         */
        class BestFit extends JMenuItem {
            public BestFit() {
                super("Best fit column");
                addActionListener(
                    evt -> {
                        if (currentPoint != null) {
                            int column = currentTable.columnAtPoint(currentPoint);
                            int maxWidth = getMaxColumnWidth(column);
                            currentTable.getColumnModel().getColumn(column).setPreferredWidth(
                                maxWidth);
                        }
                    });
            }
        }

        class ColorPanel extends JMenuItem {
            public ColorPanel() {
                super("Color settings...");
                setIcon(ChainsawIcons.ICON_PREFERENCES);
                addActionListener(
                    evt -> showColorPreferences());
            }
        }

        class LogPanelPreferences extends JMenuItem {
            public LogPanelPreferences() {
                super("Tab Preferences...");
                setIcon(ChainsawIcons.ICON_PREFERENCES);
                addActionListener(
                    evt -> showPreferences());
            }
        }

        class FocusOn extends JMenuItem {
            public FocusOn() {
                super("Set 'refine focus' field to value under pointer");
                addActionListener(
                    evt -> {
                        if (currentPoint != null) {
                            String operator = "==";
                            int column = currentTable.columnAtPoint(currentPoint);
                            int row = currentTable.rowAtPoint(currentPoint);
                            String colName = currentTable.getColumnName(column).toUpperCase();
                            String value = getValueOf(row, column);

                            if (columnNameKeywordMap.containsKey(colName)) {
                                filterText.setText(
                                    columnNameKeywordMap.get(colName).toString() + " " + operator
                                        + " '" + value + "'");
                            }
                        }
                    });
            }
        }

        class DefineAddCustomFilter extends JMenuItem {
            public DefineAddCustomFilter() {
                super("Add value under pointer to 'refine focus' field");
                addActionListener(
                    evt -> {
                        if (currentPoint != null) {
                            String operator = "==";
                            int column = currentTable.columnAtPoint(currentPoint);
                            int row = currentTable.rowAtPoint(currentPoint);
                            String value = getValueOf(row, column);
                            String colName = currentTable.getColumnName(column).toUpperCase();

                            if (columnNameKeywordMap.containsKey(colName)) {
                                filterText.setText(
                                    filterText.getText() + " && "
                                        + columnNameKeywordMap.get(colName).toString() + " "
                                        + operator + " '" + value + "'");
                            }

                        }
                    });
            }
        }

        class DefineAddCustomFind extends JMenuItem {
            public DefineAddCustomFind() {
                super("Add value under pointer to 'find' field");
                addActionListener(
                    evt -> {
                        if (currentPoint != null) {
                            String operator = "==";
                            int column = currentTable.columnAtPoint(currentPoint);
                            int row = currentTable.rowAtPoint(currentPoint);
                            String value = getValueOf(row, column);
                            String colName = currentTable.getColumnName(column).toUpperCase();

                            if (columnNameKeywordMap.containsKey(colName)) {
                                findCombo.setSelectedItem(
                                    findText.getText() + " && "
                                        + columnNameKeywordMap.get(colName).toString() + " "
                                        + operator + " '" + value + "'");
                                findNext();
                            }
                        }
                    });
            }
        }

        class BuildColorRule extends JMenuItem {
            public BuildColorRule() {
                super("Define color rule for value under pointer");
                addActionListener(
                    evt -> {
                        if (currentPoint != null) {
                            String operator = "==";
                            int column = currentTable.columnAtPoint(currentPoint);
                            int row = currentTable.rowAtPoint(currentPoint);
                            String colName = currentTable.getColumnName(column).toUpperCase();
                            String value = getValueOf(row, column);

                            if (columnNameKeywordMap.containsKey(colName)) {
                                Color c = JColorChooser.showDialog(getRootPane(), "Choose a color", Color.red);
                                if (c != null) {
                                    String expression = columnNameKeywordMap.get(colName).toString() + " " + operator + " '" + value + "'";
                                    colorizer.addRule(new ColorRule(expression,
                                        ExpressionRule.getRule(expression), c, ChainsawConstants.COLOR_DEFAULT_FOREGROUND));
                                }
                            }
                        }
                    });
            }
        }

        final JPopupMenu mainPopup = new JPopupMenu();
        final JPopupMenu searchPopup = new JPopupMenu();

        class ClearFocus extends AbstractAction {
            public ClearFocus() {
                super("Clear 'refine focus' field");
            }

            public void actionPerformed(ActionEvent e) {
                filterText.setText(null);
                tableRuleMediator.setFilterRule(null);
                searchRuleMediator.setFilterRule(null);
            }
        }

        class CopySelection extends AbstractAction {
            public CopySelection() {
                super("Copy selection to clipboard");
            }

            public void actionPerformed(ActionEvent e) {
                if (currentTable == null) {
                    return;
                }
                int start = currentTable.getSelectionModel().getMinSelectionIndex();
                int end = currentTable.getSelectionModel().getMaxSelectionIndex();
                StringBuilder result = new StringBuilder();
                for (int row = start; row < end + 1; row++) {
                    for (int column = 0; column < currentTable.getColumnCount(); column++) {
                        result.append(getValueOf(row, column));
                        if (column != (currentTable.getColumnCount() - 1)) {
                            result.append(" - ");
                        }
                    }
                    result.append(System.getProperty("line.separator"));
                }
                StringSelection selection = new StringSelection(result.toString());
                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                clipboard.setContents(selection, null);
            }
        }

        class CopyField extends AbstractAction {
            public CopyField() {
                super("Copy value under pointer to clipboard");
            }

            public void actionPerformed(ActionEvent e) {
                if (currentPoint != null && currentTable != null) {
                    int column = currentTable.columnAtPoint(currentPoint);
                    int row = currentTable.rowAtPoint(currentPoint);
                    String value = getValueOf(row, column);
                    StringSelection selection = new StringSelection(value);
                    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                    clipboard.setContents(selection, null);
                }
            }
        }
        final JMenuItem menuItemToggleDock = new JMenuItem("Undock/dock");

        dockingAction =
            new AbstractAction("Undock") {
                public void actionPerformed(ActionEvent evt) {
                    if (isDocked()) {
                        undock();
                    } else {
                        dock();
                    }
                }
            };
        dockingAction.putValue(
            Action.SMALL_ICON, new ImageIcon(ChainsawIcons.UNDOCK));
        menuItemToggleDock.setAction(dockingAction);

        /*
         * Popup definition
         */
        mainPopup.add(new FocusOn());
        searchPopup.add(new FocusOn());
        mainPopup.add(new DefineAddCustomFilter());
        searchPopup.add(new DefineAddCustomFilter());
        mainPopup.add(new ClearFocus());
        searchPopup.add(new ClearFocus());

        mainPopup.add(new JSeparator());
        searchPopup.add(new JSeparator());

        class Search extends JMenuItem {
            public Search() {
                super("Find value under pointer");

                addActionListener(
                    evt -> {
                        if (currentPoint != null) {
                            String operator = "==";
                            int column = currentTable.columnAtPoint(currentPoint);
                            int row = currentTable.rowAtPoint(currentPoint);
                            String colName = currentTable.getColumnName(column).toUpperCase();
                            String value = getValueOf(row, column);
                            if (columnNameKeywordMap.containsKey(colName)) {
                                findCombo.setSelectedItem(
                                    columnNameKeywordMap.get(colName).toString() + " " + operator
                                        + " '" + value + "'");
                                findNext();
                            }
                        }
                    });
            }
        }

        class ClearSearch extends AbstractAction {
            public ClearSearch() {
                super("Clear find field");
            }

            public void actionPerformed(ActionEvent e) {
                findCombo.setSelectedItem(null);
                updateFindRule(null);
            }
        }

        mainPopup.add(new Search());
        searchPopup.add(new Search());
        mainPopup.add(new DefineAddCustomFind());
        searchPopup.add(new DefineAddCustomFind());
        mainPopup.add(new ClearSearch());
        searchPopup.add(new ClearSearch());

        mainPopup.add(new JSeparator());
        searchPopup.add(new JSeparator());

        class DisplayNormalTimes extends JMenuItem {
            public DisplayNormalTimes() {
                super("Hide relative times");
                addActionListener(
                    e -> {
                        if (currentPoint != null) {
                            ((TableColorizingRenderer) currentTable.getDefaultRenderer(Object.class)).setUseNormalTimes();
                            ((ChainsawCyclicBufferTableModel) currentTable.getModel()).reFilter();
                            setEnabled(true);
                        }
                    });
            }
        }

        class DisplayRelativeTimesToRowUnderCursor extends JMenuItem {
            public DisplayRelativeTimesToRowUnderCursor() {
                super("Show times relative to this event");
                addActionListener(
                    e -> {
                        if (currentPoint != null) {
                            int row = currentTable.rowAtPoint(currentPoint);
                            ChainsawCyclicBufferTableModel cyclicBufferTableModel = (ChainsawCyclicBufferTableModel) currentTable.getModel();
                            LoggingEventWrapper loggingEventWrapper = cyclicBufferTableModel.getRow(row);
                            if (loggingEventWrapper != null) {
                                ((TableColorizingRenderer) currentTable.getDefaultRenderer(Object.class)).setUseRelativeTimes(loggingEventWrapper.getLoggingEvent().m_timestamp.atZone(ZoneId.systemDefault()));
                                cyclicBufferTableModel.reFilter();
                            }
                            setEnabled(true);
                        }
                    });
            }
        }

        class DisplayRelativeTimesToPreviousRow extends JMenuItem {
            public DisplayRelativeTimesToPreviousRow() {
                super("Show times relative to previous rows");
                addActionListener(
                    e -> {
                        if (currentPoint != null) {
                            ((TableColorizingRenderer) currentTable.getDefaultRenderer(Object.class)).setUseRelativeTimesToPreviousRow();
                            ((ChainsawCyclicBufferTableModel) currentTable.getModel()).reFilter();
                            setEnabled(true);
                        }
                    });
            }
        }

        mainPopup.add(new DisplayRelativeTimesToRowUnderCursor());
        searchPopup.add(new DisplayRelativeTimesToRowUnderCursor());
        mainPopup.add(new DisplayRelativeTimesToPreviousRow());
        searchPopup.add(new DisplayRelativeTimesToPreviousRow());
        mainPopup.add(new DisplayNormalTimes());
        searchPopup.add(new DisplayNormalTimes());
        mainPopup.add(new JSeparator());
        searchPopup.add(new JSeparator());

        mainPopup.add(new BuildColorRule());
        searchPopup.add(new BuildColorRule());
        mainPopup.add(new JSeparator());
        searchPopup.add(new JSeparator());
        mainPopup.add(new CopyField());
        mainPopup.add(new CopySelection());
        searchPopup.add(new CopyField());
        searchPopup.add(new CopySelection());
        mainPopup.add(new JSeparator());
        searchPopup.add(new JSeparator());

        mainPopup.add(menuItemToggleDetails);
        mainPopup.add(menuItemLoggerTree);
        mainToggleToolTips = new ToggleToolTips();
        searchToggleToolTips = new ToggleToolTips();
        mainPopup.add(mainToggleToolTips);
        searchPopup.add(searchToggleToolTips);

        mainPopup.add(new JSeparator());

        mainPopup.add(menuItemToggleDock);

        mainPopup.add(new BestFit());
        searchPopup.add(new BestFit());

        mainPopup.add(new JSeparator());

        mainPopup.add(new ColorPanel());
        searchPopup.add(new ColorPanel());
        mainPopup.add(new LogPanelPreferences());
        searchPopup.add(new LogPanelPreferences());

        final PopupListener mainTablePopupListener = new PopupListener(mainPopup);
        eventsPane.addMouseListener(mainTablePopupListener);
        table.addMouseListener(mainTablePopupListener);

        table.addMouseListener(new MouseListener() {
            public void mouseClicked(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            public void mousePressed(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            public void mouseReleased(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            public void mouseEntered(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            public void mouseExited(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            private void checkMultiSelect(MouseEvent mouseEvent) {
                if (mouseEvent.isAltDown()) {
                    table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
                } else {
                    table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                }
            }
        });


        searchTable.addMouseListener(new MouseListener() {
            public void mouseClicked(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            public void mousePressed(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            public void mouseReleased(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            public void mouseEntered(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            public void mouseExited(MouseEvent mouseEvent) {
                checkMultiSelect(mouseEvent);
            }

            private void checkMultiSelect(MouseEvent mouseEvent) {
                if (mouseEvent.isAltDown()) {
                    searchTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
                } else {
                    searchTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                }
            }
        });


        final PopupListener searchTablePopupListener = new PopupListener(searchPopup);
        searchPane.addMouseListener(searchTablePopupListener);
        searchTable.addMouseListener(searchTablePopupListener);

        loadSettings();
    }