blob: 7e077d83f9c3ebaf074ca2b7f06c815dd5151e3a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.log4j.chainsaw;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import org.apache.log4j.chainsaw.color.ColorPanel;
import org.apache.log4j.chainsaw.color.RuleColorizer;
import org.apache.log4j.chainsaw.filter.FilterModel;
import org.apache.log4j.chainsaw.helper.SwingHelper;
import org.apache.log4j.chainsaw.icons.ChainsawIcons;
import org.apache.log4j.chainsaw.icons.LineIconFactory;
import org.apache.log4j.chainsaw.layout.DefaultLayoutFactory;
import org.apache.log4j.chainsaw.layout.EventDetailLayout;
import org.apache.log4j.chainsaw.layout.LayoutEditorPane;
import org.apache.log4j.chainsaw.prefs.LoadSettingsEvent;
import org.apache.log4j.chainsaw.prefs.Profileable;
import org.apache.log4j.chainsaw.prefs.SaveSettingsEvent;
import org.apache.log4j.chainsaw.prefs.SettingsManager;
import org.apache.log4j.chainsaw.xstream.TableColumnConverter;
import org.apache.log4j.helpers.Constants;
import org.apache.log4j.rule.ColorRule;
import org.apache.log4j.rule.ExpressionRule;
import org.apache.log4j.rule.Rule;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.text.Document;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.List;
import org.apache.commons.configuration2.AbstractConfiguration;
import org.apache.log4j.chainsaw.logevents.ChainsawLoggingEvent;
import org.apache.log4j.chainsaw.logevents.Level;
import org.apache.log4j.spi.LoggingEventFieldResolver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* A LogPanel provides a view to a collection of LoggingEvents.<br>
* <br>
* As events are received, the keywords in the 'tab identifier' application
* preference are replaced with the values from the received event. The
* main application uses this expression to route received LoggingEvents to
* individual LogPanels which match each event's resolved expression.<br>
* <br>
* The LogPanel's capabilities can be broken up into four areas:<br>
* <ul><li> toolbar - provides 'find' and 'refine focus' features
* <li> logger tree - displays a tree of the logger hierarchy, which can be used
* to filter the display
* <li> table - displays the events which pass the filtering rules
* <li>detail panel - displays information about the currently selected event
* </ul>
* Here is a complete list of LogPanel's capabilities:<br>
* <ul><li>display selected LoggingEvent row number and total LoggingEvent count
* <li>pause or unpause reception of LoggingEvents
* <li>configure, load and save column settings (displayed columns, order, width)
* <li>configure, load and save color rules
* filter displayed LoggingEvents based on the logger tree settings
* <li>filter displayed LoggingEvents based on a 'refine focus' expression
* (evaluates only those LoggingEvents which pass the logger tree filter
* <li>colorize LoggingEvents based on expressions
* <li>hide, show and configure the detail pane and tooltip
* <li>configure the formatting of the logger, level and timestamp fields
* <li>dock or undock
* <li>table displays first line of exception, but when cell is clicked, a
* popup opens to display the full stack trace
* <li>find
* <li>scroll to bottom
* <li>sort
* <li>provide a context menu which can be used to build color or display expressions
* <li>hide or show the logger tree
* <li>toggle the container storing the LoggingEvents to use either a
* CyclicBuffer (defaults to max size of 5000, but configurable through
* CHAINSAW_CAPACITY system property) or ArrayList (no max size)
* <li>use the mouse context menu to 'best-fit' columns, define display
* expression filters based on mouse location and access other capabilities
* </ul>
*
* @author Scott Deboy (sdeboy at apache.org)
* @author Paul Smith (psmith at apache.org)
* @author Stephen Pain
* @author Isuru Suriarachchi
* @see org.apache.log4j.chainsaw.color.ColorPanel
* @see org.apache.log4j.rule.ExpressionRule
* @see org.apache.log4j.spi.LoggingEventFieldResolver
*/
public class LogPanel extends DockablePanel implements ChainsawEventBatchListener {
private static final DateFormat TIMESTAMP_DATE_FORMAT = new SimpleDateFormat(Constants.TIMESTAMP_RULE_FORMAT);
private static final double DEFAULT_DETAIL_SPLIT_LOCATION = 0.71d;
private static final double DEFAULT_LOG_TREE_SPLIT_LOCATION = 0.2d;
private final String identifier;
private final ChainsawStatusBar statusBar;
private final JFrame logPanelPreferencesFrame = new JFrame();
private ColorPanel colorPanel;
private final JFrame colorFrame = new JFrame();
private final JFrame undockedFrame;
private final DockablePanel externalPanel;
private final Action dockingAction;
private final JToolBar undockedToolbar;
private final JSortTable table;
private final TableColorizingRenderer renderer;
private final EventContainer tableModel;
private final JEditorPane detail;
private final JSplitPane lowerPanel;
private final DetailPaneUpdater detailPaneUpdater;
private final JPanel detailPanel = new JPanel(new BorderLayout());
private final JSplitPane nameTreeAndMainPanelSplit;
private final LoggerNameTreePanel logTreePanel;
private final LogPanelPreferenceModel preferenceModel = new LogPanelPreferenceModel();
private ApplicationPreferenceModel applicationPreferenceModel;
private final LogPanelPreferencePanel logPanelPreferencesPanel;
private final FilterModel filterModel = new FilterModel();
private final RuleColorizer colorizer = new RuleColorizer();
private final RuleMediator tableRuleMediator = new RuleMediator(false);
private final RuleMediator searchRuleMediator = new RuleMediator(true);
private final EventDetailLayout detailLayout = new EventDetailLayout();
private double lastLogTreePanelSplitLocation = DEFAULT_LOG_TREE_SPLIT_LOCATION;
private Point currentPoint;
private JTable currentTable;
private boolean paused = false;
private Rule findRule;
private String currentFindRuleText;
private Rule findMarkerRule;
private final int dividerSize;
static final String TABLE_COLUMN_ORDER = "table.columns.order";
static final String TABLE_COLUMN_WIDTHS = "table.columns.widths";
static final String COLORS_EXTENSION = ".colors";
private static final int LOG_PANEL_SERIALIZATION_VERSION_NUMBER = 2; //increment when format changes
private int previousLastIndex = -1;
private final Logger logger = LogManager.getLogger();
private AutoFilterComboBox filterCombo;
private AutoFilterComboBox findCombo;
private JScrollPane eventsPane;
private int currentSearchMatchCount;
private Rule clearTableExpressionRule;
private int lowerPanelDividerLocation;
private EventContainer searchModel;
private final JSortTable searchTable;
private TableColorizingRenderer searchRenderer;
private ToggleToolTips mainToggleToolTips;
private ToggleToolTips searchToggleToolTips;
private JScrollPane detailPane;
private JScrollPane searchPane;
//only one tableCellEditor, shared by both tables
private TableCellEditor markerCellEditor;
private JToolBar detailToolbar;
private boolean searchResultsDisplayed;
private ColorizedEventAndSearchMatchThumbnail colorizedEventAndSearchMatchThumbnail;
private EventTimeDeltaMatchThumbnail eventTimeDeltaMatchThumbnail;
private boolean isDetailPanelVisible;
private ChainsawReceiver m_receiver;
/**
* Creates a new LogPanel object. If a LogPanel with this identifier has
* been loaded previously, reload settings saved on last exit.
*
* @param statusBar shared status bar, provided by main application
* @param identifier used to load and save settings
*/
public LogPanel(final ChainsawStatusBar statusBar,
final String identifier,
int cyclicBufferSize,
Map<String, RuleColorizer> allColorizers,
final ApplicationPreferenceModel applicationPreferenceModel) {
this.identifier = identifier;
this.statusBar = statusBar;
this.applicationPreferenceModel = applicationPreferenceModel;
this.logPanelPreferencesPanel = new LogPanelPreferencePanel(preferenceModel, applicationPreferenceModel);
logger.debug("creating logpanel for {}", 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();
preferenceModel.addPropertyChangeListener(
"scrollToBottom",
evt -> {
boolean value = (Boolean) evt.getNewValue();
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 -> preferenceModel.setLogTreePanelVisible(
menuItemLoggerTree.isSelected()));
menuItemLoggerTree.setIcon(new ImageIcon(ChainsawIcons.WINDOW_ICON));
final JCheckBoxMenuItem menuItemToggleDetails =
new JCheckBoxMenuItem("Show Detail Pane");
menuItemToggleDetails.addActionListener(
e -> preferenceModel.setDetailPaneVisible(
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
*/
preferenceModel.addPropertyChangeListener("wrapMessage",
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
boolean wrap = (Boolean) evt.getNewValue();
renderer.setWrapMessage(wrap);
table.tableChanged(new TableModelEvent(tableModel));
searchRenderer.setWrapMessage(wrap);
searchTable.tableChanged(new TableModelEvent(searchModel));
}
});
preferenceModel.addPropertyChangeListener("searchResultsVisible",
evt -> {
boolean displaySearchResultsInDetailsIfAvailable = (Boolean) evt.getNewValue();
if (displaySearchResultsInDetailsIfAvailable) {
showSearchResults();
} else {
hideSearchResults();
}
});
preferenceModel.addPropertyChangeListener("highlightSearchMatchText",
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
boolean highlightText = (Boolean) evt.getNewValue();
renderer.setHighlightSearchMatchText(highlightText);
table.tableChanged(new TableModelEvent(tableModel));
searchRenderer.setHighlightSearchMatchText(highlightText);
searchTable.tableChanged(new TableModelEvent(searchModel));
}
});
preferenceModel.addPropertyChangeListener(
"detailPaneVisible",
evt -> {
boolean detailPaneVisible = (Boolean) evt.getNewValue();
if (detailPaneVisible) {
showDetailPane();
} else {
//don't hide the detail pane if search results are being displayed
if (!searchResultsDisplayed) {
hideDetailPane();
}
}
});
preferenceModel.addPropertyChangeListener(
"logTreePanelVisible",
evt -> {
boolean newValue = (Boolean) evt.getNewValue();
if (newValue) {
showLogTreePanel();
} else {
hideLogTreePanel();
}
});
preferenceModel.addPropertyChangeListener("toolTips",
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
boolean toolTips = (Boolean) evt.getNewValue();
renderer.setToolTipsVisible(toolTips);
searchRenderer.setToolTipsVisible(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);
preferenceModel.addPropertyChangeListener("clearTableExpression", evt -> {
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;
}
});
preferenceModel.addPropertyChangeListener("loggerPrecision",
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
LogPanelPreferenceModel model = (LogPanelPreferenceModel) evt.getSource();
renderer.setLoggerPrecision(model.getLoggerPrecision());
table.tableChanged(new TableModelEvent(tableModel));
searchRenderer.setLoggerPrecision(model.getLoggerPrecision());
searchTable.tableChanged(new TableModelEvent(searchModel));
}
});
preferenceModel.addPropertyChangeListener("toolTips",
evt -> {
boolean value = (Boolean) evt.getNewValue();
searchToggleToolTips.setSelected(value);
mainToggleToolTips.setSelected(value);
});
preferenceModel.addPropertyChangeListener(
"logTreePanelVisible",
evt -> {
boolean value = (Boolean) evt.getNewValue();
menuItemLoggerTree.setSelected(value);
});
preferenceModel.addPropertyChangeListener(
"detailPaneVisible",
evt -> {
boolean value = (Boolean) evt.getNewValue();
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, preferenceModel, this, colorizer, filterModel);
logTreePanel.getLoggerVisibilityRule().addPropertyChangeListener(evt -> {
if (evt.getPropertyName().equals("searchExpression")) {
findCombo.setSelectedItem(evt.getNewValue().toString());
findNext();
}
});
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(colorizer, filterModel, allColorizers, applicationPreferenceModel);
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) {
preferenceModel.setDetailPaneVisible(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(ChainsawConstants.DEFAULT_COLOR_RULE_NAME, 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();
}
private String getValueOf(int row, int column) {
if (currentTable == null) {
return "";
}
Object o = currentTable.getValueAt(row, column);
if (o instanceof Date) {
return TIMESTAMP_DATE_FORMAT.format((Date) o);
}
if (o instanceof String) {
return (String) o;
}
if (o instanceof org.apache.log4j.chainsaw.logevents.Level) {
return o.toString();
}
if (o instanceof String[]) {
StringBuilder value = new StringBuilder();
//exception - build message + throwable
String[] ti = (String[]) o;
if (ti.length > 0 && (!(ti.length == 1 && ti[0].equals("")))) {
LoggingEventWrapper loggingEventWrapper = ((ChainsawCyclicBufferTableModel) (currentTable.getModel())).getRow(row);
value = new StringBuilder(loggingEventWrapper.getLoggingEvent().m_message);
for (int i = 0; i < ((String[]) o).length; i++) {
value.append('\n').append(((String[]) o)[i]);
}
}
return value.toString();
}
return "";
}
private Action getFindNextAction() {
final Action action =
new AbstractAction("Find next") {
public void actionPerformed(ActionEvent e) {
findNext();
}
};
// action.putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_F));
action.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("F3"));
action.putValue(
Action.SHORT_DESCRIPTION,
"Find the next occurrence of the rule from the current row");
action.putValue(Action.SMALL_ICON, new ImageIcon(ChainsawIcons.DOWN));
return action;
}
private Action getFindPreviousAction() {
final Action action =
new AbstractAction("Find previous") {
public void actionPerformed(ActionEvent e) {
findPrevious();
}
};
// action.putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_F));
action.putValue(
Action.ACCELERATOR_KEY,
KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.SHIFT_MASK));
action.putValue(
Action.SHORT_DESCRIPTION,
"Find the previous occurrence of the rule from the current row");
action.putValue(Action.SMALL_ICON, new ImageIcon(ChainsawIcons.UP));
return action;
}
private void buildCombo(final AutoFilterComboBox combo, boolean isFiltering, final AutoFilterComboBox.AutoFilterComboBoxModel otherModel) {
//add (hopefully useful) default filters
combo.addItem("LEVEL == TRACE");
combo.addItem("LEVEL >= DEBUG");
combo.addItem("LEVEL >= INFO");
combo.addItem("LEVEL >= WARN");
combo.addItem("LEVEL >= ERROR");
combo.addItem("LEVEL == FATAL");
final JTextField filterText = (JTextField) combo.getEditor().getEditorComponent();
if (isFiltering) {
filterText.getDocument().addDocumentListener(new DelayedTextDocumentListener(filterText));
}
filterText.setToolTipText("Enter an expression - right click or ctrl-space for menu - press enter to add to list");
filterText.addKeyListener(new ExpressionRuleContext(filterModel, filterText));
if (combo.getEditor().getEditorComponent() instanceof JTextField) {
combo.addActionListener(
new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("comboBoxEdited")) {
try {
//verify the expression is valid
Object item = combo.getSelectedItem();
if (item != null && !item.toString().trim().equals("")) {
ExpressionRule.getRule(item.toString());
//add entry as first row of the combo box
combo.insertItemAt(item, 0);
otherModel.insertElementAt(item, 0);
}
//valid expression, reset background color in case we were previously an invalid expression
filterText.setBackground(UIManager.getColor("TextField.background"));
} catch (IllegalArgumentException iae) {
//don't add expressions that aren't valid
//invalid expression, change background of the field
filterText.setToolTipText(iae.getMessage());
filterText.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
}
}
}
});
}
}
/**
* Accessor
*
* @return scrollToBottom
*/
public boolean isScrollToBottom() {
return preferenceModel.isScrollToBottom();
}
public void setRefineFocusText(String refineFocusText) {
final JTextField filterText = (JTextField) filterCombo.getEditor().getEditorComponent();
filterText.setText(refineFocusText);
}
public String getRefineFocusText() {
final JTextField filterText = (JTextField) filterCombo.getEditor().getEditorComponent();
return filterText.getText();
}
/**
* Mutator
*/
public void toggleScrollToBottom() {
preferenceModel.setScrollToBottom(!preferenceModel.isScrollToBottom());
}
private void scrollToBottom() {
//run this in an invokeLater block to ensure this action is enqueued to the end of the EDT
EventQueue.invokeLater(() -> {
int scrollRow = tableModel.getRowCount() - 1;
table.scrollToRow(scrollRow);
});
}
public void scrollToTop() {
EventQueue.invokeLater(() -> {
if (tableModel.getRowCount() > 1) {
table.scrollToRow(0);
}
});
}
/**
* Accessor
*
* @return namespace
* @see Profileable
*/
public String getNamespace() {
return getIdentifier();
}
/**
* Accessor
*
* @return identifier
* @see EventBatchListener
*/
public String getInterestedIdentifier() {
return getIdentifier();
}
/**
* Load settings from the panel preference model
*
*/
private void loadSettings() {
logger.info( "Loading settings for panel with identifier {}", identifier );
AbstractConfiguration config = SettingsManager.getInstance().getSettingsForReceiverTab(identifier);
Iterator<String> iter = config.getKeys();
while( iter.hasNext() ){
logger.debug( "Key: {}", iter.next() );
}
lowerPanelDividerLocation = config.getInt( "logpanel.lowerPanelDividerLocation" );
int treeDividerLocation = config.getInt( "logpanel.treeDividerLocation" );
String conversionPattern = config.getString( "logpanel.conversionPattern", DefaultLayoutFactory.getDefaultPatternLayout() );
String[] savedComboSettings = config.getStringArray("logpanel.savedComboSettings" );
for( String comboSetting : savedComboSettings ){
filterCombo.insertItemAt(comboSetting, 0);
findCombo.insertItemAt(comboSetting, 0);
}
String[] columnsOrder = config.getStringArray( "table.columns.order" );
Integer[] columnWidths = (Integer[])config.getArray(Integer.class, "table.columns.widths" );
TableColumnModel columnModel = table.getColumnModel();
for( int index = 0; index < columnsOrder.length; index++ ){
logger.debug( "Loading column {}", columnsOrder[index] );
TableColumn column = new TableColumn(index);
column.setHeaderValue(columnsOrder[index]);
preferenceModel.addColumn(column);
}
boolean isCyclic = config.getBoolean( "logpanel.cyclic" );
tableModel.setCyclic( isCyclic );
searchModel.setCyclic( isCyclic );
lowerPanel.setDividerLocation(config.getInt( "logpanel.lowerPanelDividerLocation" ));
nameTreeAndMainPanelSplit.setDividerLocation(config.getInt( "logpanel.treeDividerLocation" ));
detailLayout.setConversionPattern(conversionPattern);
undockedFrame.setLocation(0, 0);
undockedFrame.setSize(new Dimension(1024, 768));
logTreePanel.ignore(Arrays.asList(config.getStringArray("logpanel.hiddenLoggers")));
logTreePanel.setHiddenExpression(config.getString("logpanel.hiddenExpression"));
logTreePanel.setAlwaysDisplayExpression(config.getString("logpanel.alwaysDisplayExpression"));
String clearTableExpression = config.getString("logpanel.clearTableExpression", null);
if (clearTableExpression != null && clearTableExpression.length() > 1) {
try {
clearTableExpressionRule = ExpressionRule.getRule(clearTableExpression);
} catch (Exception e) {
clearTableExpressionRule = null;
}
}
colorizer.loadColorSettings(config);
// if (xmlFile.exists()) {
// XStream stream = buildXStreamForLogPanelPreference();
// ObjectInputStream in = null;
// try {
// logger.info("configuration for panel exists: " + xmlFile + " - " + identifier + ", loading");
// FileReader r = new FileReader(xmlFile);
// in = stream.createObjectInputStream(r);
// LogPanelPreferenceModel storedPrefs = (LogPanelPreferenceModel) in.readObject();
// lowerPanelDividerLocation = in.readInt();
// int treeDividerLocation = in.readInt();
// String conversionPattern = in.readObject().toString();
// //this version number is checked to identify whether there is a Vector comming next
// int versionNumber = 0;
// try {
// versionNumber = in.readInt();
// } catch (EOFException eof) {
// }
//
// Vector savedVector;
// //read the vector only if the version number is greater than 0. higher version numbers can be
// //used in the future to save more data structures
// if (versionNumber > 0) {
// savedVector = (Vector) in.readObject();
// for (Object item : savedVector) {
// //insert each row at index zero (so last row in vector will be row zero)
// filterCombo.insertItemAt(item, 0);
// findCombo.insertItemAt(item, 0);
// }
// if (versionNumber > 1) {
// //update prefModel columns to include defaults
// int index = 0;
// String columnOrder = event.getSetting(TABLE_COLUMN_ORDER);
// StringTokenizer tok = new StringTokenizer(columnOrder, ",");
// while (tok.hasMoreElements()) {
// String element = tok.nextElement().toString().trim().toUpperCase();
// TableColumn column = new TableColumn(index++);
// column.setHeaderValue(element);
// preferenceModel.addColumn(column);
// }
//
// TableColumnModel columnModel = table.getColumnModel();
// //remove previous columns
// while (columnModel.getColumnCount() > 0) {
// columnModel.removeColumn(columnModel.getColumn(0));
// }
// //add visible column order columns
// for (Object o1 : preferenceModel.getVisibleColumnOrder()) {
// TableColumn col = (TableColumn) o1;
// columnModel.addColumn(col);
// }
//
// TableColumnModel searchColumnModel = searchTable.getColumnModel();
// //remove previous columns
// while (searchColumnModel.getColumnCount() > 0) {
// searchColumnModel.removeColumn(searchColumnModel.getColumn(0));
// }
// //add visible column order columns
// for (Object o : preferenceModel.getVisibleColumnOrder()) {
// TableColumn col = (TableColumn) o;
// searchColumnModel.addColumn(col);
// }
//
// preferenceModel.apply(storedPrefs);
// } else {
// loadDefaultColumnSettings(event);
// }
// //ensure tablemodel cyclic flag is updated
// //may be panel configs that don't have these values
// tableModel.setCyclic(preferenceModel.isCyclic());
// searchModel.setCyclic(preferenceModel.isCyclic());
// lowerPanel.setDividerLocation(lowerPanelDividerLocation);
// nameTreeAndMainPanelSplit.setDividerLocation(treeDividerLocation);
// detailLayout.setConversionPattern(conversionPattern);
// undockedFrame.setLocation(0, 0);
// undockedFrame.setSize(new Dimension(1024, 768));
// } else {
// loadDefaultColumnSettings(event);
// }
// } catch (Exception e) {
// logger.info("unable to load configuration for panel: " + xmlFile + " - " + identifier + " - using default settings", e);
// loadDefaultColumnSettings(event);
// // TODO need to log this..
// } finally {
// if (in != null) {
// try {
// in.close();
// } catch (IOException ioe) {
// }
// }
// }
// } else {
// //not setting lower panel divider location here - will do that after the UI is visible
// loadDefaultColumnSettings(event);
// }
// //ensure tablemodel cyclic flag is updated
// tableModel.setCyclic(preferenceModel.isCyclic());
// searchModel.setCyclic(preferenceModel.isCyclic());
// logTreePanel.ignore(preferenceModel.getHiddenLoggers());
// logTreePanel.setHiddenExpression(preferenceModel.getHiddenExpression());
// logTreePanel.setAlwaysDisplayExpression(preferenceModel.getAlwaysDisplayExpression());
// if (preferenceModel.getClearTableExpression() != null) {
// try {
// clearTableExpressionRule = ExpressionRule.getRule(preferenceModel.getClearTableExpression());
// } catch (Exception e) {
// clearTableExpressionRule = null;
// }
// }
//
// //attempt to load color settings - no need to URL encode the identifier
// colorizer.loadColorSettings(identifier);
}
/**
* Save preferences to the panel preference model
*
* @param event
* @see LogPanelPreferenceModel
*/
public void saveSettings(SaveSettingsEvent event) {
File xmlFile;
try {
xmlFile = new File(SettingsManager.getInstance().getSettingsDirectory(), URLEncoder.encode(identifier, "UTF-8") + ".xml");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//unable to save..just return
return;
}
preferenceModel.setHiddenLoggers(new HashSet(logTreePanel.getHiddenSet()));
preferenceModel.setHiddenExpression(logTreePanel.getHiddenExpression());
preferenceModel.setAlwaysDisplayExpression(logTreePanel.getAlwaysDisplayExpression());
List visibleOrder = new ArrayList();
Enumeration<TableColumn> cols = table.getColumnModel().getColumns();
while (cols.hasMoreElements()) {
TableColumn c = cols.nextElement();
visibleOrder.add(c);
}
preferenceModel.setVisibleColumnOrder(visibleOrder);
//search table will use same columns as main table
XStream stream = buildXStreamForLogPanelPreference();
ObjectOutputStream s = null;
try {
FileWriter w = new FileWriter(xmlFile);
s = stream.createObjectOutputStream(w);
s.writeObject(preferenceModel);
if (isDetailPanelVisible) {
//use current size
s.writeInt(lowerPanel.getDividerLocation());
} else {
//use size when last hidden
s.writeInt(lowerPanelDividerLocation);
}
s.writeInt(nameTreeAndMainPanelSplit.getDividerLocation());
s.writeObject(detailLayout.getConversionPattern());
//this is a version number written to the file to identify that there is a Vector serialized after this
s.writeInt(LOG_PANEL_SERIALIZATION_VERSION_NUMBER);
//don't write filterexpressionvector, write the combobox's model's backing vector
Vector combinedVector = new Vector();
combinedVector.addAll(filterCombo.getModelData());
combinedVector.addAll(findCombo.getModelData());
//duplicates will be removed when loaded..
s.writeObject(combinedVector);
} catch (Exception ex) {
ex.printStackTrace();
// TODO need to log this..
} finally {
if (s != null) {
try {
s.close();
} catch (IOException ioe) {
}
}
}
//no need to URL encode the identifier
// colorizer.saveColorSettings(identifier);
}
private XStream buildXStreamForLogPanelPreference() {
XStream stream = new XStream(new DomDriver());
stream.registerConverter(new TableColumnConverter());
return stream;
}
/**
* Display the panel preferences frame
*/
void showPreferences() {
//don't pack this frame
centerAndSetVisible(logPanelPreferencesFrame);
}
public static void centerAndSetVisible(Window window) {
Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
window.setLocation(new Point((screenDimension.width / 2) - (window.getSize().width / 2),
(screenDimension.height / 2) - (window.getSize().height / 2)));
window.setVisible(true);
}
/**
* Display the color rule frame
*/
void showColorPreferences() {
colorPanel.loadLogPanelColorizers();
colorFrame.pack();
centerAndSetVisible(colorFrame);
}
/**
* Toggle panel preference for detail visibility on or off
*/
void toggleDetailVisible() {
preferenceModel.setDetailPaneVisible(
!preferenceModel.isDetailPaneVisible());
}
/**
* Accessor
*
* @return detail visibility flag
*/
boolean isDetailVisible() {
return preferenceModel.isDetailPaneVisible();
}
boolean isSearchResultsVisible() {
return preferenceModel.isSearchResultsVisible();
}
/**
* Toggle panel preference for logger tree visibility on or off
*/
void toggleLogTreeVisible() {
preferenceModel.setLogTreePanelVisible(
!preferenceModel.isLogTreePanelVisible());
}
/**
* Accessor
*
* @return logger tree visibility flag
*/
boolean isLogTreeVisible() {
return preferenceModel.isLogTreePanelVisible();
}
/**
* Return all events
*
* @return list of LoggingEvents
*/
List getEvents() {
return tableModel.getAllEvents();
}
/**
* Return the events that are visible with the current filter applied
*
* @return list of LoggingEvents
*/
List getFilteredEvents() {
return tableModel.getFilteredEvents();
}
List<LoggingEventWrapper> getMatchingEvents(Rule rule) {
return tableModel.getMatchingEvents(rule);
}
/**
* Remove all events
*/
void clearEvents() {
clearModel();
}
/**
* Accessor
*
* @return identifier
*/
String getIdentifier() {
return identifier;
}
/**
* Undocks this DockablePanel by removing the panel from the LogUI window
* and placing it inside it's own JFrame.
*/
void undock() {
final int row = table.getSelectedRow();
setDocked(false);
externalPanel.removeAll();
externalPanel.add(undockedToolbar, BorderLayout.NORTH);
externalPanel.add(nameTreeAndMainPanelSplit, BorderLayout.CENTER);
externalPanel.setDocked(false);
undockedFrame.pack();
undockedFrame.setVisible(true);
dockingAction.putValue(Action.NAME, "Dock");
dockingAction.putValue(Action.SMALL_ICON, ChainsawIcons.ICON_DOCK);
if (row > -1) {
EventQueue.invokeLater(() -> table.scrollToRow(row));
}
}
/**
* Add an eventCountListener
*
* @param l
*/
void addEventCountListener(EventCountListener l) {
tableModel.addEventCountListener(l);
}
/**
* Accessor
*
* @return paused flag
*/
boolean isPaused() {
return paused;
}
/**
* Modifies the Paused property and notifies the listeners
*
* @param paused
*/
void setPaused(boolean paused) {
boolean oldValue = this.paused;
this.paused = paused;
firePropertyChange("paused", oldValue, paused);
}
/**
* Change the selected event on the log panel. Will cause scrollToBottom to be turned off.
*
* @param eventNumber
* @return row number or -1 if row with log4jid property with that number was not found
*/
int setSelectedEvent(int eventNumber) {
int row = tableModel.locate(ExpressionRule.getRule("prop.log4jid == " + eventNumber), 0, true);
if (row > -1) {
preferenceModel.setScrollToBottom(false);
table.scrollToRow(row);
}
return row;
}
/**
* Add a preference propertyChangeListener
*
* @param listener
*/
void addPreferencePropertyChangeListener(PropertyChangeListener listener) {
preferenceModel.addPropertyChangeListener(listener);
}
/**
* Toggle the LoggingEvent container from either managing a cyclic buffer of
* events or an ArrayList of events
*/
void toggleCyclic() {
boolean toggledCyclic = !preferenceModel.isCyclic();
preferenceModel.setCyclic(toggledCyclic);
tableModel.setCyclic(toggledCyclic);
searchModel.setCyclic(toggledCyclic);
}
/**
* Accessor
*
* @return flag answering if LoggingEvent container is a cyclic buffer
*/
boolean isCyclic() {
return preferenceModel.isCyclic();
}
public void updateFindRule(String ruleText) {
if ((ruleText == null) || (ruleText.trim().equals(""))) {
findRule = null;
tableModel.updateEventsWithFindRule(null);
colorizer.setFindRule(null);
tableRuleMediator.setFindRule(null);
searchRuleMediator.setFindRule(null);
//reset background color in case we were previously an invalid expression
findCombo.setBackground(UIManager.getColor("TextField.background"));
findCombo.setToolTipText(
"Enter an expression - right click or ctrl-space for menu - press enter to add to list");
currentSearchMatchCount = 0;
currentFindRuleText = null;
statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
//if the preference to show search results is enabled, the find rule is now null - hide search results
if (isSearchResultsVisible()) {
hideSearchResults();
}
} else {
//only turn off scrolltobottom when finding something (find not empty)
preferenceModel.setScrollToBottom(false);
if (ruleText.equals(currentFindRuleText)) {
//don't update events if rule hasn't changed (we're finding next/previous)
return;
}
currentFindRuleText = ruleText;
try {
final JTextField findText = (JTextField) findCombo.getEditor().getEditorComponent();
findText.setToolTipText(
"Enter an expression - right click or ctrl-space for menu - press enter to add to list");
findRule = ExpressionRule.getRule(ruleText);
currentSearchMatchCount = tableModel.updateEventsWithFindRule(findRule);
searchModel.updateEventsWithFindRule(findRule);
colorizer.setFindRule(findRule);
tableRuleMediator.setFindRule(findRule);
searchRuleMediator.setFindRule(findRule);
//valid expression, reset background color in case we were previously an invalid expression
findText.setBackground(UIManager.getColor("TextField.background"));
statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
if (isSearchResultsVisible()) {
showSearchResults();
}
} catch (IllegalArgumentException re) {
findRule = null;
final JTextField findText = (JTextField) findCombo.getEditor().getEditorComponent();
findText.setToolTipText(re.getMessage());
findText.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
colorizer.setFindRule(null);
tableRuleMediator.setFindRule(null);
searchRuleMediator.setFindRule(null);
tableModel.updateEventsWithFindRule(null);
searchModel.updateEventsWithFindRule(null);
currentSearchMatchCount = 0;
statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
//if the preference to show search results is enabled, the find rule is now null - hide search results
if (isSearchResultsVisible()) {
hideSearchResults();
}
}
}
}
private void hideSearchResults() {
if (searchResultsDisplayed) {
detailPanel.removeAll();
JPanel leftSpacePanel = new JPanel();
Integer scrollBarWidth = (Integer) UIManager.get("ScrollBar.width");
leftSpacePanel.setPreferredSize(new Dimension(scrollBarWidth - 4, -1));
JPanel rightSpacePanel = new JPanel();
rightSpacePanel.setPreferredSize(new Dimension(scrollBarWidth - 4, -1));
detailPanel.add(detailToolbar, BorderLayout.NORTH);
detailPanel.add(detailPane, BorderLayout.CENTER);
detailPanel.add(leftSpacePanel, BorderLayout.WEST);
detailPanel.add(rightSpacePanel, BorderLayout.EAST);
detailPanel.revalidate();
detailPanel.repaint();
//if the detail visible pref is not enabled, hide the detail pane
searchResultsDisplayed = false;
//hide if pref is not enabled
if (!isDetailVisible()) {
hideDetailPane();
}
}
}
private void showSearchResults() {
if (isSearchResultsVisible() && !searchResultsDisplayed && findRule != null) {
//if pref is set, always update detail panel to contain search results
detailPanel.removeAll();
detailPanel.add(searchPane, BorderLayout.CENTER);
Integer scrollBarWidth = (Integer) UIManager.get("ScrollBar.width");
JPanel leftSpacePanel = new JPanel();
leftSpacePanel.setPreferredSize(new Dimension(scrollBarWidth - 4, -1));
JPanel rightSpacePanel = new JPanel();
rightSpacePanel.setPreferredSize(new Dimension(scrollBarWidth - 4, -1));
detailPanel.add(leftSpacePanel, BorderLayout.WEST);
detailPanel.add(rightSpacePanel, BorderLayout.EAST);
detailPanel.revalidate();
detailPanel.repaint();
//if the detail visible pref is not enabled, show the detail pane
searchResultsDisplayed = true;
//show if pref is not enabled
if (!isDetailVisible()) {
showDetailPane();
}
}
}
/**
* Display the detail pane, using the last known divider location
*/
private void showDetailPane() {
if (!isDetailPanelVisible) {
lowerPanel.setDividerSize(dividerSize);
if (lowerPanelDividerLocation == 0) {
lowerPanel.setDividerLocation(DEFAULT_DETAIL_SPLIT_LOCATION);
lowerPanelDividerLocation = lowerPanel.getDividerLocation();
} else {
lowerPanel.setDividerLocation(lowerPanelDividerLocation);
}
detailPanel.setVisible(true);
detailPanel.repaint();
lowerPanel.repaint();
isDetailPanelVisible = true;
}
}
/**
* Hide the detail pane, holding the current divider location for later use
*/
private void hideDetailPane() {
//may be called not currently visible on initial setup to ensure panel is not visible..only update divider location if hiding when currently visible
if (isDetailPanelVisible) {
lowerPanelDividerLocation = lowerPanel.getDividerLocation();
}
lowerPanel.setDividerSize(0);
detailPanel.setVisible(false);
lowerPanel.repaint();
isDetailPanelVisible = false;
}
/**
* Display the log tree pane, using the last known divider location
*/
private void showLogTreePanel() {
nameTreeAndMainPanelSplit.setDividerSize(dividerSize);
nameTreeAndMainPanelSplit.setDividerLocation(
lastLogTreePanelSplitLocation);
logTreePanel.setVisible(true);
nameTreeAndMainPanelSplit.repaint();
}
/**
* Hide the log tree pane, holding the current divider location for later use
*/
private void hideLogTreePanel() {
//subtract one to make sizes match
int currentSize = nameTreeAndMainPanelSplit.getWidth() - nameTreeAndMainPanelSplit.getDividerSize() - 1;
if (currentSize > 0) {
lastLogTreePanelSplitLocation =
(double) nameTreeAndMainPanelSplit.getDividerLocation() / currentSize;
}
nameTreeAndMainPanelSplit.setDividerSize(0);
logTreePanel.setVisible(false);
nameTreeAndMainPanelSplit.repaint();
}
/**
* Return a toolbar used by the undocked LogPanel's frame
*
* @return toolbar
*/
private JToolBar createDockwindowToolbar() {
final JToolBar toolbar = new JToolBar();
toolbar.setFloatable(false);
final Action dockPauseAction =
new AbstractAction("Pause") {
public void actionPerformed(ActionEvent evt) {
setPaused(!isPaused());
}
};
dockPauseAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_P);
dockPauseAction.putValue(
Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("F12"));
dockPauseAction.putValue(
Action.SHORT_DESCRIPTION,
"Halts the display, while still allowing events to stream in the background");
dockPauseAction.putValue(
Action.SMALL_ICON, new ImageIcon(ChainsawIcons.PAUSE));
final SmallToggleButton dockPauseButton =
new SmallToggleButton(dockPauseAction);
dockPauseButton.setText("");
dockPauseButton.getModel().setSelected(isPaused());
addPropertyChangeListener(
"paused",
evt -> dockPauseButton.getModel().setSelected(isPaused()));
toolbar.add(dockPauseButton);
Action dockShowPrefsAction =
new AbstractAction("") {
public void actionPerformed(ActionEvent arg0) {
showPreferences();
}
};
dockShowPrefsAction.putValue(
Action.SHORT_DESCRIPTION, "Define preferences...");
dockShowPrefsAction.putValue(
Action.SMALL_ICON, ChainsawIcons.ICON_PREFERENCES);
toolbar.add(new SmallButton(dockShowPrefsAction));
Action dockToggleLogTreeAction =
new AbstractAction() {
public void actionPerformed(ActionEvent e) {
toggleLogTreeVisible();
}
};
dockToggleLogTreeAction.putValue(Action.SHORT_DESCRIPTION, "Toggles the Logger Tree Pane");
dockToggleLogTreeAction.putValue("enabled", Boolean.TRUE);
dockToggleLogTreeAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_T);
dockToggleLogTreeAction.putValue(
Action.ACCELERATOR_KEY,
KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
dockToggleLogTreeAction.putValue(
Action.SMALL_ICON, new ImageIcon(ChainsawIcons.WINDOW_ICON));
final SmallToggleButton toggleLogTreeButton =
new SmallToggleButton(dockToggleLogTreeAction);
preferenceModel.addPropertyChangeListener("logTreePanelVisible", evt -> toggleLogTreeButton.setSelected(preferenceModel.isLogTreePanelVisible()));
toggleLogTreeButton.setSelected(isLogTreeVisible());
toolbar.add(toggleLogTreeButton);
toolbar.addSeparator();
final Action undockedClearAction =
new AbstractAction("Clear") {
public void actionPerformed(ActionEvent arg0) {
clearModel();
}
};
undockedClearAction.putValue(
Action.SMALL_ICON, new ImageIcon(ChainsawIcons.DELETE));
undockedClearAction.putValue(
Action.SHORT_DESCRIPTION, "Removes all the events from the current view");
final SmallButton dockClearButton = new SmallButton(undockedClearAction);
dockClearButton.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
undockedClearAction.getValue(Action.NAME));
dockClearButton.getActionMap().put(
undockedClearAction.getValue(Action.NAME), undockedClearAction);
dockClearButton.setText("");
toolbar.add(dockClearButton);
toolbar.addSeparator();
Action dockToggleScrollToBottomAction =
new AbstractAction("Toggles Scroll to Bottom") {
public void actionPerformed(ActionEvent e) {
toggleScrollToBottom();
}
};
dockToggleScrollToBottomAction.putValue(Action.SHORT_DESCRIPTION, "Toggles Scroll to Bottom");
dockToggleScrollToBottomAction.putValue("enabled", Boolean.TRUE);
dockToggleScrollToBottomAction.putValue(
Action.SMALL_ICON, new ImageIcon(ChainsawIcons.SCROLL_TO_BOTTOM));
final SmallToggleButton toggleScrollToBottomButton =
new SmallToggleButton(dockToggleScrollToBottomAction);
preferenceModel.addPropertyChangeListener("scrollToBottom", evt -> toggleScrollToBottomButton.setSelected(isScrollToBottom()));
toggleScrollToBottomButton.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
KeyStroke.getKeyStroke(KeyEvent.VK_B, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
dockToggleScrollToBottomAction.getValue(Action.NAME));
toggleScrollToBottomButton.getActionMap().put(
dockToggleScrollToBottomAction.getValue(Action.NAME), dockToggleScrollToBottomAction);
toggleScrollToBottomButton.setSelected(isScrollToBottom());
toggleScrollToBottomButton.setText("");
toolbar.add(toggleScrollToBottomButton);
toolbar.addSeparator();
findCombo.addActionListener(e -> {
//comboboxchanged event received when text is modified in the field..when enter is pressed, it's comboboxedited
if (e.getActionCommand().equalsIgnoreCase("comboBoxEdited")) {
findNext();
}
});
Action redockAction =
new AbstractAction("", ChainsawIcons.ICON_DOCK) {
public void actionPerformed(ActionEvent arg0) {
dock();
}
};
redockAction.putValue(
Action.SHORT_DESCRIPTION,
"Docks this window back with the main Chainsaw window");
SmallButton redockButton = new SmallButton(redockAction);
toolbar.add(redockButton);
return toolbar;
}
/**
* Update the status bar with current selected row and row count
*/
protected void updateStatusBar() {
SwingHelper.invokeOnEDT(
() -> {
statusBar.setSelectedLine(
table.getSelectedRow() + 1, tableModel.getRowCount(),
tableModel.size(), getIdentifier());
statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
});
}
/**
* Update the detail pane layout text
*
* @param conversionPattern layout text
*/
private void setDetailPaneConversionPattern(String conversionPattern) {
String oldPattern = getDetailPaneConversionPattern();
(detailLayout).setConversionPattern(conversionPattern);
firePropertyChange(
"detailPaneConversionPattern", oldPattern,
getDetailPaneConversionPattern());
}
/**
* Accessor
*
* @return conversionPattern layout text
*/
private String getDetailPaneConversionPattern() {
return (detailLayout).getConversionPattern();
}
private void setDetailPaneDatetimeFormat(DateTimeFormatter datetimeFormat){
DateTimeFormatter oldFormat = getDetailPaneDatetimeFormat();
detailLayout.setDateformat(datetimeFormat);
firePropertyChange(
"detailPaneDatetimeFormat", oldFormat,
getDetailPaneDatetimeFormat());
}
private DateTimeFormatter getDetailPaneDatetimeFormat() {
return detailLayout.getDateformat();
}
/**
* Reset the LoggingEvent container, detail panel and status bar
*/
private void clearModel() {
previousLastIndex = -1;
tableModel.clearModel();
searchModel.clearModel();
synchronized (detail) {
detailPaneUpdater.setSelectedRow(-1);
detail.notify();
}
statusBar.setNothingSelected();
}
public void findNextColorizedEvent() {
EventQueue.invokeLater(() -> {
final int nextRow = tableModel.findColoredRow(table.getSelectedRow() + 1, true);
if (nextRow > -1) {
table.scrollToRow(nextRow);
}
});
}
public void findPreviousColorizedEvent() {
EventQueue.invokeLater(() -> {
final int previousRow = tableModel.findColoredRow(table.getSelectedRow() - 1, false);
if (previousRow > -1) {
table.scrollToRow(previousRow);
}
});
}
/**
* Finds the next row matching the current find rule, and ensures it is made
* visible
*/
public void findNext() {
Object item = findCombo.getSelectedItem();
updateFindRule(item == null ? null : item.toString());
if (findRule != null) {
EventQueue.invokeLater(() -> {
final JTextField findText = (JTextField) findCombo.getEditor().getEditorComponent();
try {
int filteredEventsSize = getFilteredEvents().size();
int startRow = table.getSelectedRow() + 1;
if (startRow > filteredEventsSize - 1) {
startRow = 0;
}
//no selected row would return -1, so we'd start at row zero
final int nextRow = tableModel.locate(findRule, startRow, true);
if (nextRow > -1) {
table.scrollToRow(nextRow);
findText.setToolTipText("Enter an expression - right click or ctrl-space for menu - press enter to add to list");
}
findText.setBackground(UIManager.getColor("TextField.background"));
} catch (IllegalArgumentException iae) {
findText.setToolTipText(iae.getMessage());
findText.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
colorizer.setFindRule(null);
tableRuleMediator.setFindRule(null);
searchRuleMediator.setFindRule(null);
}
});
}
}
/**
* Finds the previous row matching the current find rule, and ensures it is made
* visible
*/
public void findPrevious() {
Object item = findCombo.getSelectedItem();
updateFindRule(item == null ? null : item.toString());
if (findRule != null) {
EventQueue.invokeLater(() -> {
final JTextField findText = (JTextField) findCombo.getEditor().getEditorComponent();
try {
int startRow = table.getSelectedRow() - 1;
int filteredEventsSize = getFilteredEvents().size();
if (startRow < 0) {
startRow = filteredEventsSize - 1;
}
final int previousRow = tableModel.locate(findRule, startRow, false);
if (previousRow > -1) {
table.scrollToRow(previousRow);
findCombo.setToolTipText("Enter an expression - right click or ctrl-space for menu - press enter to add to list");
}
findText.setBackground(UIManager.getColor("TextField.background"));
} catch (IllegalArgumentException iae) {
findText.setToolTipText(iae.getMessage());
findText.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
}
});
}
}
/**
* Docks this DockablePanel by hiding the JFrame and placing the Panel back
* inside the LogUI window.
*/
private void dock() {
final int row = table.getSelectedRow();
setDocked(true);
undockedFrame.setVisible(false);
removeAll();
add(nameTreeAndMainPanelSplit, BorderLayout.CENTER);
externalPanel.setDocked(true);
dockingAction.putValue(Action.NAME, "Undock");
dockingAction.putValue(Action.SMALL_ICON, ChainsawIcons.ICON_UNDOCK);
if (row > -1) {
EventQueue.invokeLater(() -> table.scrollToRow(row));
}
}
/**
* Load default column settings if no settings exist for this identifier
*
* @param event
*/
private void loadDefaultColumnSettings(LoadSettingsEvent event) {
String columnOrder = event.getSetting(TABLE_COLUMN_ORDER);
TableColumnModel columnModel = table.getColumnModel();
TableColumnModel searchColumnModel = searchTable.getColumnModel();
Map<String, TableColumn> columnNameMap = new HashMap<>();
Map<String, TableColumn> searchColumnNameMap = new HashMap<>();
for (int i = 0; i < columnModel.getColumnCount(); i++) {
columnNameMap.put(table.getColumnName(i).toUpperCase(), columnModel.getColumn(i));
}
for (int i = 0; i < searchColumnModel.getColumnCount(); i++) {
searchColumnNameMap.put(searchTable.getColumnName(i).toUpperCase(), searchColumnModel.getColumn(i));
}
int index;
StringTokenizer tok = new StringTokenizer(columnOrder, ",");
List<TableColumn> sortedColumnList = new ArrayList<>();
/*
remove all columns from the table that exist in the model
and add in the correct order to a new arraylist
(may be a subset of possible columns)
**/
while (tok.hasMoreElements()) {
String element = tok.nextElement().toString().trim().toUpperCase();
TableColumn column = columnNameMap.get(element);
if (column != null) {
sortedColumnList.add(column);
table.removeColumn(column);
searchTable.removeColumn(column);
}
}
preferenceModel.setDetailPaneVisible(event.asBoolean("detailPaneVisible"));
preferenceModel.setLogTreePanelVisible(event.asBoolean("logTreePanelVisible"));
preferenceModel.setHighlightSearchMatchText(event.asBoolean("highlightSearchMatchText"));
preferenceModel.setWrapMessage(event.asBoolean("wrapMessage"));
preferenceModel.setSearchResultsVisible(event.asBoolean("searchResultsVisible"));
//re-add columns to the table in the order provided from the list
for (Object aSortedColumnList : sortedColumnList) {
TableColumn element = (TableColumn) aSortedColumnList;
if (preferenceModel.addColumn(element)) {
if (!applicationPreferenceModel.isDefaultColumnsSet() || applicationPreferenceModel.isDefaultColumnsSet() &&
applicationPreferenceModel.getDefaultColumnNames().contains(element.getHeaderValue())) {
table.addColumn(element);
searchTable.addColumn(element);
preferenceModel.setColumnVisible(element.getHeaderValue().toString(), true);
}
}
}
String columnWidths = event.getSetting(TABLE_COLUMN_WIDTHS);
tok = new StringTokenizer(columnWidths, ",");
index = 0;
while (tok.hasMoreElements()) {
String element = (String) tok.nextElement();
try {
int width = Integer.parseInt(element);
if (index > (columnModel.getColumnCount() - 1)) {
logger.warn(
"loadsettings - failed attempt to set width for index " + index
+ ", width " + element);
} else {
columnModel.getColumn(index).setPreferredWidth(width);
searchColumnModel.getColumn(index).setPreferredWidth(width);
}
index++;
} catch (NumberFormatException e) {
logger.error("Error decoding a Table width", e);
}
}
undockedFrame.setSize(getSize());
undockedFrame.setLocation(getBounds().x, getBounds().y);
repaint();
}
/**
* Iterate over all values in the column and return the longest width
*
* @param index column index
* @return longest width - relies on FontMetrics.stringWidth for calculation
*/
private int getMaxColumnWidth(int index) {
FontMetrics metrics = getGraphics().getFontMetrics();
int longestWidth =
metrics.stringWidth(" " + table.getColumnName(index) + " ")
+ (2 * table.getColumnModel().getColumnMargin());
for (int i = 0, j = tableModel.getRowCount(); i < j; i++) {
Component c =
renderer.getTableCellRendererComponent(
table, table.getValueAt(i, index), false, false, i, index);
if (c instanceof JLabel) {
longestWidth =
Math.max(longestWidth, metrics.stringWidth(((JLabel) c).getText()));
}
}
return longestWidth + 5;
}
private String getToolTipTextForEvent(LoggingEventWrapper loggingEventWrapper) {
return detailLayout.format(loggingEventWrapper.getLoggingEvent());
}
/**
* ensures the Entry map of all the unque logger names etc, that is used for
* the Filter panel is updated with any new information from the event
*
* @param event
*/
private void updateOtherModels(ChainsawLoggingEvent event) {
/*
* EventContainer is a LoggerNameModel imp, use that for notifing
*/
tableModel.addLoggerName(event.m_logger);
filterModel.processNewLoggingEvent(event);
}
public void findNextMarker() {
EventQueue.invokeLater(() -> {
int startRow = table.getSelectedRow() + 1;
int filteredEventsSize = getFilteredEvents().size();
if (startRow > filteredEventsSize - 1) {
startRow = 0;
}
final int nextRow = tableModel.locate(findMarkerRule, startRow, true);
if (nextRow > -1) {
table.scrollToRow(nextRow);
}
});
}
public void findPreviousMarker() {
EventQueue.invokeLater(() -> {
int startRow = table.getSelectedRow() - 1;
int filteredEventsSize = getFilteredEvents().size();
if (startRow < 0) {
startRow = filteredEventsSize - 1;
}
final int previousRow = tableModel.locate(findMarkerRule, startRow, false);
if (previousRow > -1) {
table.scrollToRow(previousRow);
}
});
}
public void clearAllMarkers() {
//this will get the properties to be removed from both tables..but
tableModel.removePropertyFromEvents(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
}
public void toggleMarker() {
int row = table.getSelectedRow();
if (row != -1) {
LoggingEventWrapper loggingEventWrapper = tableModel.getRow(row);
if (loggingEventWrapper != null) {
Object marker = loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
if (marker == null) {
loggingEventWrapper.setProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE, "set");
} else {
loggingEventWrapper.removeProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
}
//if marker -was- null, it no longer is (may need to add the column)
tableModel.fireRowUpdated(row, (marker == null));
}
}
}
public void layoutComponents() {
if (preferenceModel.isDetailPaneVisible()) {
showDetailPane();
} else {
hideDetailPane();
}
}
public void setFindText(String findText) {
findCombo.setSelectedItem(findText);
findNext();
}
public String getFindText() {
Object selectedItem = findCombo.getSelectedItem();
if (selectedItem == null) {
return "";
}
return selectedItem.toString();
}
public void setReceiver( ChainsawReceiver rx ){
m_receiver = rx;
m_receiver.addPropertyChangeListener(((pce) -> {
if( pce.getPropertyName().equals( "name" ) ){
// this.identifier = pce.getNewValue();
}
}));
m_receiver.addChainsawEventBatchListener(this);
}
public void receiveChainsawEventBatch(List<ChainsawLoggingEvent> events){
SwingHelper.invokeOnEDT(() -> {
/*
* if this panel is paused, we totally ignore events
*/
if (isPaused()) {
return;
}
final int selectedRow = table.getSelectedRow();
final int startingRow = table.getRowCount();
final LoggingEventWrapper selectedEvent;
if (selectedRow >= 0) {
selectedEvent = tableModel.getRow(selectedRow);
} else {
selectedEvent = null;
}
final int startingSearchRow = searchTable.getRowCount();
boolean rowAdded = false;
boolean searchRowAdded = false;
int addedRowCount = 0;
int searchAddedRowCount = 0;
for (ChainsawLoggingEvent event1 : events) {
//create two separate loggingEventWrappers (main table and search table), as they have different info on display state
LoggingEventWrapper loggingEventWrapper1 = new LoggingEventWrapper(event1);
//if the clearTableExpressionRule is not null, evaluate & clear the table if it matches
if (clearTableExpressionRule != null && clearTableExpressionRule.evaluate(event1, null)) {
logger.info("clear table expression matched - clearing table - matching event msg - " + event1.m_message);
clearEvents();
}
updateOtherModels(event1);
boolean isCurrentRowAdded = tableModel.isAddRow(loggingEventWrapper1);
if (isCurrentRowAdded) {
addedRowCount++;
}
rowAdded = rowAdded || isCurrentRowAdded;
//create a new loggingEventWrapper via copy constructor to ensure same IDs
LoggingEventWrapper loggingEventWrapper2 = new LoggingEventWrapper(loggingEventWrapper1);
boolean isSearchCurrentRowAdded = searchModel.isAddRow(loggingEventWrapper2);
if (isSearchCurrentRowAdded) {
searchAddedRowCount++;
}
searchRowAdded = searchRowAdded || isSearchCurrentRowAdded;
}
//fire after adding all events
if (rowAdded) {
tableModel.fireTableEvent(startingRow, startingRow + addedRowCount, addedRowCount);
}
if (searchRowAdded) {
searchModel.fireTableEvent(startingSearchRow, startingSearchRow + searchAddedRowCount, searchAddedRowCount);
}
//tell the model to notify the count listeners
tableModel.notifyCountListeners();
if (rowAdded) {
if (tableModel.isSortEnabled()) {
tableModel.sort();
}
//always update detail pane (since we may be using a cyclic buffer which is full)
detailPaneUpdater.setSelectedRow(table.getSelectedRow());
}
if (searchRowAdded) {
if (searchModel.isSortEnabled()) {
searchModel.sort();
}
}
if (!isScrollToBottom() && selectedEvent != null) {
final int newIndex = tableModel.getRowIndex(selectedEvent);
if (newIndex >= 0) {
// Don't scroll, just maintain selection...
table.setRowSelectionInterval(newIndex, newIndex);
}
}
});
}
/**
* This class receives notification when the Refine focus or find field is
* updated, where a background thread periodically wakes up and checks if
* they have stopped typing yet. This ensures that the filtering of the
* model is not done for every single character typed.
*
* @author Paul Smith psmith
*/
private final class DelayedTextDocumentListener
implements DocumentListener {
private static final long CHECK_PERIOD = 1000;
private final JTextField textField;
private long lastTimeStamp = System.currentTimeMillis();
private final Thread delayThread;
private final String defaultToolTip;
private String lastText = "";
private DelayedTextDocumentListener(final JTextField textFeld) {
super();
this.textField = textFeld;
this.defaultToolTip = textFeld.getToolTipText();
this.delayThread =
new Thread(
() -> {
while (true) {
try {
Thread.sleep(CHECK_PERIOD);
} catch (InterruptedException e) {
}
if (
(System.currentTimeMillis() - lastTimeStamp) < CHECK_PERIOD) {
// They typed something since the last check. we ignor
// this for a sample period
// logger.debug("Typed something since the last check");
} else if (
(System.currentTimeMillis() - lastTimeStamp) < (2 * CHECK_PERIOD)) {
// they stopped typing recently, but have stopped for at least
// 1 sample period. lets apply the filter
// logger.debug("Typed something recently applying filter");
if (!(textFeld.getText().trim().equals(lastText.trim()))) {
lastText = textFeld.getText();
EventQueue.invokeLater(DelayedTextDocumentListener.this::setFilter);
}
} else {
// they stopped typing a while ago, let's forget about it
// logger.debug(
// "They stoppped typing a while ago, assuming filter has been applied");
}
}
});
delayThread.setPriority(Thread.MIN_PRIORITY);
delayThread.start();
}
/**
* Update timestamp
*
* @param e
*/
public void insertUpdate(DocumentEvent e) {
notifyChange();
}
/**
* Update timestamp
*
* @param e
*/
public void removeUpdate(DocumentEvent e) {
notifyChange();
}
/**
* Update timestamp
*
* @param e
*/
public void changedUpdate(DocumentEvent e) {
notifyChange();
}
/**
* Update timestamp
*/
private void notifyChange() {
this.lastTimeStamp = System.currentTimeMillis();
}
/**
* Update refinement rule based on the entered expression.
*/
private void setFilter() {
if (textField.getText().trim().equals("")) {
//reset background color in case we were previously an invalid expression
textField.setBackground(UIManager.getColor("TextField.background"));
tableRuleMediator.setFilterRule(null);
searchRuleMediator.setFilterRule(null);
textField.setToolTipText(defaultToolTip);
if (findRule != null) {
currentSearchMatchCount = tableModel.getSearchMatchCount();
statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
}
} else {
try {
tableRuleMediator.setFilterRule(ExpressionRule.getRule(textField.getText()));
searchRuleMediator.setFilterRule(ExpressionRule.getRule(textField.getText()));
textField.setToolTipText(defaultToolTip);
if (findRule != null) {
currentSearchMatchCount = tableModel.getSearchMatchCount();
statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
}
//valid expression, reset background color in case we were previously an invalid expression
textField.setBackground(UIManager.getColor("TextField.background"));
} catch (IllegalArgumentException iae) {
//invalid expression, change background of the field
textField.setToolTipText(iae.getMessage());
textField.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
if (findRule != null) {
currentSearchMatchCount = tableModel.getSearchMatchCount();
statusBar.setSearchMatchCount(currentSearchMatchCount, getIdentifier());
}
}
}
}
}
private final class TableMarkerListener extends MouseAdapter {
private JTable markerTable;
private EventContainer markerEventContainer;
private EventContainer otherMarkerEventContainer;
private TableMarkerListener(JTable markerTable, EventContainer markerEventContainer, EventContainer otherMarkerEventContainer) {
this.markerTable = markerTable;
this.markerEventContainer = markerEventContainer;
this.otherMarkerEventContainer = otherMarkerEventContainer;
}
public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 2) {
int row = markerTable.rowAtPoint(evt.getPoint());
if (row != -1) {
LoggingEventWrapper loggingEventWrapper = markerEventContainer.getRow(row);
if (loggingEventWrapper != null) {
Object marker = loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
if (marker == null) {
loggingEventWrapper.setProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE, "set");
} else {
loggingEventWrapper.removeProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
}
//if marker -was- null, it no longer is (may need to add the column)
markerEventContainer.fireRowUpdated(row, (marker == null));
otherMarkerEventContainer.fireRowUpdated(otherMarkerEventContainer.getRowIndex(loggingEventWrapper), (marker == null));
}
}
}
}
}
/**
* Update active tooltip
*/
private final class TableColumnDetailMouseListener extends MouseMotionAdapter {
private int currentRow = -1;
private JTable detailTable;
private EventContainer detailEventContainer;
private TableColumnDetailMouseListener(JTable detailTable, EventContainer detailEventContainer) {
this.detailTable = detailTable;
this.detailEventContainer = detailEventContainer;
}
/**
* Update tooltip based on mouse position
*
* @param evt
*/
public void mouseMoved(MouseEvent evt) {
currentPoint = evt.getPoint();
currentTable = detailTable;
if (preferenceModel.isToolTips()) {
int row = detailTable.rowAtPoint(evt.getPoint());
if ((row == currentRow) || (row == -1)) {
return;
}
currentRow = row;
LoggingEventWrapper event = detailEventContainer.getRow(currentRow);
if (event != null) {
String toolTipText = getToolTipTextForEvent(event);
detailTable.setToolTipText(toolTipText);
}
} else {
detailTable.setToolTipText(null);
}
}
}
//if columnmoved or columnremoved callback received, re-apply table's sort index based
//sort column name
private class ChainsawTableColumnModelListener implements TableColumnModelListener {
private JSortTable modelListenerTable;
private ChainsawTableColumnModelListener(JSortTable modelListenerTable) {
this.modelListenerTable = modelListenerTable;
}
public void columnAdded(TableColumnModelEvent e) {
//no-op
}
/**
* Update sorted column
*
* @param e
*/
public void columnRemoved(TableColumnModelEvent e) {
modelListenerTable.updateSortedColumn();
}
/**
* Update sorted column
*
* @param e
*/
public void columnMoved(TableColumnModelEvent e) {
modelListenerTable.updateSortedColumn();
}
/**
* Ignore margin changed
*
* @param e
*/
public void columnMarginChanged(ChangeEvent e) {
}
/**
* Ignore selection changed
*
* @param e
*/
public void columnSelectionChanged(ListSelectionEvent e) {
}
}
/**
* Thread that periodically checks if the selected row has changed, and if
* it was, updates the Detail Panel with the detailed Logging information
*/
private class DetailPaneUpdater implements PropertyChangeListener {
private int selectedRow = -1;
int lastRow = -1;
private DetailPaneUpdater() {
}
/**
* Update detail pane to display information about the LoggingEvent at index row
*
* @param row
*/
private void setSelectedRow(int row) {
selectedRow = row;
updateDetailPane();
}
private void setAndUpdateSelectedRow(int row) {
selectedRow = row;
updateDetailPane(true);
}
private void updateDetailPane() {
updateDetailPane(false);
}
/**
* Update detail pane
*/
private void updateDetailPane(boolean force) {
/*
* Don't bother doing anything if it's not visible. Note: the isVisible() method on
* Component is not really accurate here because when the button to toggle display of
* the detail pane is triggered it still appears as 'visible' for some reason.
*/
if (!preferenceModel.isDetailPaneVisible()) {
return;
}
LoggingEventWrapper loggingEventWrapper = null;
if (force || (selectedRow != -1 && (lastRow != selectedRow))) {
loggingEventWrapper = tableModel.getRow(selectedRow);
if (loggingEventWrapper != null) {
final StringBuilder buf = new StringBuilder();
buf.append(detailLayout.format(loggingEventWrapper.getLoggingEvent()));
if (buf.length() > 0) {
try {
final Document doc = detail.getEditorKit().createDefaultDocument();
detail.getEditorKit().read(new StringReader(buf.toString()), doc, 0);
SwingHelper.invokeOnEDT(() -> {
detail.setDocument(doc);
JTextComponentFormatter.applySystemFontAndSize(detail);
detail.setCaretPosition(0);
lastRow = selectedRow;
});
} catch (Exception e) {
}
}
}
}
if (loggingEventWrapper == null && (lastRow != selectedRow)) {
try {
final Document doc = detail.getEditorKit().createDefaultDocument();
detail.getEditorKit().read(new StringReader("<html>Nothing selected</html>"), doc, 0);
SwingHelper.invokeOnEDT(() -> {
detail.setDocument(doc);
JTextComponentFormatter.applySystemFontAndSize(detail);
detail.setCaretPosition(0);
lastRow = selectedRow;
});
} catch (Exception e) {
}
}
}
/**
* Update detail pane layout if it's changed
*
* @param arg0
*/
public void propertyChange(PropertyChangeEvent arg0) {
SwingUtilities.invokeLater(
() -> updateDetailPane(true));
}
}
private class ThrowableDisplayMouseAdapter extends MouseAdapter {
private JTable throwableTable;
private EventContainer throwableEventContainer;
final JDialog detailDialog;
final JEditorPane detailArea;
public ThrowableDisplayMouseAdapter(JTable throwableTable, EventContainer throwableEventContainer) {
this.throwableTable = throwableTable;
this.throwableEventContainer = throwableEventContainer;
detailDialog = new JDialog((JFrame) null, true);
Container container = detailDialog.getContentPane();
detailArea = new JEditorPane();
JTextComponentFormatter.applySystemFontAndSize(detailArea);
detailArea.setEditable(false);
Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
detailArea.setPreferredSize(new Dimension(screenDimension.width / 2, screenDimension.height / 2));
container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
container.add(new JScrollPane(detailArea));
detailDialog.pack();
}
public void mouseClicked(MouseEvent e) {
TableColumn column = throwableTable.getColumnModel().getColumn(throwableTable.columnAtPoint(e.getPoint()));
if (!column.getHeaderValue().toString().toUpperCase().equals(ChainsawColumns.getColumnName(ChainsawColumns.INDEX_THROWABLE_COL_NAME))) {
return;
}
LoggingEventWrapper loggingEventWrapper = throwableEventContainer.getRow(throwableTable.getSelectedRow());
//throwable string representation may be a length-one empty array
// String[] ti = loggingEventWrapper.getLoggingEvent().getThrowableStrRep();
// if (ti != null && ti.length > 0 && (!(ti.length == 1 && ti[0].equals("")))) {
// detailDialog.setTitle(throwableTable.getColumnName(throwableTable.getSelectedColumn()) + " detail...");
// StringBuilder buf = new StringBuilder();
// buf.append(loggingEventWrapper.getLoggingEvent().getMessage());
// buf.append("\n");
// for (String aTi : ti) {
// buf.append(aTi).append("\n ");
// }
//
// detailArea.setText(buf.toString());
// SwingHelper.invokeOnEDT(() -> centerAndSetVisible(detailDialog));
// }
}
}
private class MarkerCellEditor implements TableCellEditor {
JTable currentTable;
JTextField textField = new JTextField();
Set<CellEditorListener> cellEditorListeners = new HashSet<>();
private LoggingEventWrapper currentLoggingEventWrapper;
private final Object mutex = new Object();
int currentRowHeight = 0;
public Object getCellEditorValue() {
return textField.getText();
}
public boolean isCellEditable(EventObject anEvent) {
return true;
}
public boolean shouldSelectCell(EventObject anEvent) {
textField.selectAll();
return true;
}
public boolean stopCellEditing() {
if (textField.getText().trim().equals("")) {
currentLoggingEventWrapper.removeProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
} else {
currentLoggingEventWrapper.setProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE, textField.getText());
}
//row should always exist in the main table if it is being edited
tableModel.fireRowUpdated(tableModel.getRowIndex(currentLoggingEventWrapper), true);
int index = searchModel.getRowIndex(currentLoggingEventWrapper);
if (index > -1) {
searchModel.fireRowUpdated(index, true);
}
ChangeEvent event = new ChangeEvent(currentTable);
Set<CellEditorListener> cellEditorListenersCopy;
synchronized (mutex) {
cellEditorListenersCopy = new HashSet<>(cellEditorListeners);
}
for (Object aCellEditorListenersCopy : cellEditorListenersCopy) {
((CellEditorListener) aCellEditorListenersCopy).editingStopped(event);
}
currentTable.setRowHeight(currentRowHeight);
currentLoggingEventWrapper = null;
currentTable = null;
return true;
}
public void cancelCellEditing() {
Set<CellEditorListener> cellEditorListenersCopy;
synchronized (mutex) {
cellEditorListenersCopy = new HashSet<>(cellEditorListeners);
}
ChangeEvent event = new ChangeEvent(currentTable);
for (Object aCellEditorListenersCopy : cellEditorListenersCopy) {
((CellEditorListener) aCellEditorListenersCopy).editingCanceled(event);
}
currentTable.setRowHeight(currentRowHeight);
currentLoggingEventWrapper = null;
currentTable = null;
}
public void addCellEditorListener(CellEditorListener l) {
synchronized (mutex) {
cellEditorListeners.add(l);
}
}
public void removeCellEditorListener(CellEditorListener l) {
synchronized (mutex) {
cellEditorListeners.remove(l);
}
}
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
currentTable = table;
currentLoggingEventWrapper = ((EventContainer) table.getModel()).getRow(row);
if (currentLoggingEventWrapper != null) {
textField.setText(currentLoggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE));
textField.selectAll();
} else {
textField.setText("");
}
currentRowHeight = table.getRowHeight( row );
table.setRowHeight( row, textField.getPreferredSize().height );
return textField;
}
}
private class EventTimeDeltaMatchThumbnail extends AbstractEventMatchThumbnail {
public EventTimeDeltaMatchThumbnail() {
super();
initializeLists();
}
boolean primaryMatches(ThumbnailLoggingEventWrapper wrapper) {
String millisDelta = wrapper.loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE);
if (millisDelta != null && !millisDelta.trim().equals("")) {
long millisDeltaLong = Long.parseLong(millisDelta);
//arbitrary
return millisDeltaLong >= 1000;
}
return false;
}
boolean secondaryMatches(ThumbnailLoggingEventWrapper wrapper) {
//secondary is not used
return false;
}
private void initializeLists() {
secondaryList.clear();
primaryList.clear();
int i = 0;
for (Object o : tableModel.getFilteredEvents()) {
LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper) o;
ThumbnailLoggingEventWrapper wrapper = new ThumbnailLoggingEventWrapper(i, loggingEventWrapper);
i++;
//only add if there is a color defined
if (primaryMatches(wrapper)) {
primaryList.add(wrapper);
}
}
revalidate();
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int rowCount = table.getRowCount();
if (rowCount == 0) {
return;
}
//use event pane height as reference height - max component height will be extended by event height if
// last row is rendered, so subtract here
int height = eventsPane.getHeight();
int maxHeight = Math.min(maxEventHeight, (height / rowCount));
int minHeight = Math.max(1, maxHeight);
int componentHeight = height - minHeight;
int eventHeight = minHeight;
//draw all events
for (Object aPrimaryList : primaryList) {
ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper) aPrimaryList;
if (primaryMatches(wrapper)) {
float ratio = (wrapper.rowNum / (float) rowCount);
// System.out.println("error - ratio: " + ratio + ", component height: " + componentHeight);
int verticalLocation = (int) (componentHeight * ratio);
int startX = 1;
int width = getWidth() - (startX * 2);
//max out at 50, min 2...
String millisDelta = wrapper.loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE);
long millisDeltaLong = Long.parseLong(millisDelta);
long delta = Math.min(ChainsawConstants.MILLIS_DELTA_RENDERING_HEIGHT_MAX, Math.max(0, (long) (millisDeltaLong * ChainsawConstants.MILLIS_DELTA_RENDERING_FACTOR)));
float widthMaxMillisDeltaRenderRatio = ((float) width / ChainsawConstants.MILLIS_DELTA_RENDERING_HEIGHT_MAX);
int widthToUse = Math.max(2, (int) (delta * widthMaxMillisDeltaRenderRatio));
eventHeight = Math.min(maxEventHeight, eventHeight + 3);
// eventHeight = maxEventHeight;
drawEvent(applicationPreferenceModel.getDeltaColor(), (verticalLocation - eventHeight + 1), eventHeight, g, startX, widthToUse);
// System.out.println("painting error - rownum: " + wrapper.rowNum + ", location: " + verticalLocation + ", height: " + eventHeight + ", component height: " + componentHeight + ", row count: " + rowCount);
}
}
}
}
//a listener receiving color updates needs to call configureColors on this class
private class ColorizedEventAndSearchMatchThumbnail extends AbstractEventMatchThumbnail {
public ColorizedEventAndSearchMatchThumbnail() {
super();
configureColors();
}
boolean primaryMatches(ThumbnailLoggingEventWrapper wrapper) {
return !wrapper.loggingEventWrapper.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND);
}
boolean secondaryMatches(ThumbnailLoggingEventWrapper wrapper) {
return wrapper.loggingEventWrapper.isSearchMatch();
}
private void configureColors() {
secondaryList.clear();
primaryList.clear();
int i = 0;
for (Object o : tableModel.getFilteredEvents()) {
LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper) o;
ThumbnailLoggingEventWrapper wrapper = new ThumbnailLoggingEventWrapper(i, loggingEventWrapper);
if (secondaryMatches(wrapper)) {
secondaryList.add(wrapper);
}
i++;
//only add if there is a color defined
if (primaryMatches(wrapper)) {
primaryList.add(wrapper);
}
}
revalidate();
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int rowCount = table.getRowCount();
if (rowCount == 0) {
return;
}
//use event pane height as reference height - max component height will be extended by event height if
// last row is rendered, so subtract here
int height = eventsPane.getHeight();
int maxHeight = Math.min(maxEventHeight, (height / rowCount));
int minHeight = Math.max(1, maxHeight);
int componentHeight = height - minHeight;
int eventHeight = minHeight;
//draw all non error/warning/marker events
for (Object aPrimaryList1 : primaryList) {
ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper) aPrimaryList1;
if (!wrapper.loggingEventWrapper.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND)) {
if (wrapper.loggingEventWrapper.getLoggingEvent().m_level.ordinal() < Level.WARN.ordinal() && wrapper.loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE) == null) {
float ratio = (wrapper.rowNum / (float) rowCount);
// System.out.println("error - ratio: " + ratio + ", component height: " + componentHeight);
int verticalLocation = (int) (componentHeight * ratio);
int startX = 1;
int width = getWidth() - (startX * 2);
drawEvent(wrapper.loggingEventWrapper.getColorRuleBackground(), verticalLocation, eventHeight, g, startX, width);
// System.out.println("painting error - rownum: " + wrapper.rowNum + ", location: " + verticalLocation + ", height: " + eventHeight + ", component height: " + componentHeight + ", row count: " + rowCount);
}
}
}
//draw warnings, error, fatal & markers last (full width)
for (Object aPrimaryList : primaryList) {
ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper) aPrimaryList;
if (!wrapper.loggingEventWrapper.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND)) {
if (wrapper.loggingEventWrapper.getLoggingEvent().m_level.ordinal() >= Level.WARN.ordinal() || wrapper.loggingEventWrapper.getLoggingEvent().getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE) != null) {
float ratio = (wrapper.rowNum / (float) rowCount);
// System.out.println("error - ratio: " + ratio + ", component height: " + componentHeight);
int verticalLocation = (int) (componentHeight * ratio);
int startX = 1;
int width = getWidth() - (startX * 2);
//narrow the color a bit if level is less than warn
//make warnings, errors a little taller
eventHeight = Math.min(maxEventHeight, eventHeight + 3);
// eventHeight = maxEventHeight;
drawEvent(wrapper.loggingEventWrapper.getColorRuleBackground(), (verticalLocation - eventHeight + 1), eventHeight, g, startX, width);
// System.out.println("painting error - rownum: " + wrapper.rowNum + ", location: " + verticalLocation + ", height: " + eventHeight + ", component height: " + componentHeight + ", row count: " + rowCount);
}
}
}
for (Object aSecondaryList : secondaryList) {
ThumbnailLoggingEventWrapper wrapper = (ThumbnailLoggingEventWrapper) aSecondaryList;
float ratio = (wrapper.rowNum / (float) rowCount);
// System.out.println("warning - ratio: " + ratio + ", component height: " + componentHeight);
int verticalLocation = (int) (componentHeight * ratio);
int startX = 1;
int width = getWidth() - (startX * 2);
width = (width / 2);
//use black for search indicator in the 'gutter'
drawEvent(Color.BLACK, verticalLocation, eventHeight, g, startX, width);
// System.out.println("painting warning - rownum: " + wrapper.rowNum + ", location: " + verticalLocation + ", height: " + eventHeight + ", component height: " + componentHeight + ", row count: " + rowCount);
}
}
}
abstract class AbstractEventMatchThumbnail extends JPanel {
protected List<ThumbnailLoggingEventWrapper> primaryList = new ArrayList<>();
protected List<ThumbnailLoggingEventWrapper> secondaryList = new ArrayList<>();
protected final int maxEventHeight = 6;
AbstractEventMatchThumbnail() {
super();
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseMoved(MouseEvent e) {
if (preferenceModel.isThumbnailBarToolTips()) {
int yPosition = e.getPoint().y;
ThumbnailLoggingEventWrapper event = getEventWrapperAtPosition(yPosition);
if (event != null) {
setToolTipText(getToolTipTextForEvent(event.loggingEventWrapper));
}
} else {
setToolTipText(null);
}
}
});
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
int yPosition = e.getPoint().y;
ThumbnailLoggingEventWrapper event = getEventWrapperAtPosition(yPosition);
// System.out.println("rowToSelect: " + rowToSelect + ", closestRow: " + event.loggingEvent.getProperty("log4jid"));
if (event != null) {
int id = Integer.parseInt(event.loggingEventWrapper.getLoggingEvent().getProperty("log4jid"));
setSelectedEvent(id);
}
}
});
tableModel.addTableModelListener(e -> {
int firstRow = e.getFirstRow();
//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)
int lastRow = Math.min(e.getLastRow(), table.getRowCount() - 1);
//clear everything if we got an event w/-1 for first or last row
if (firstRow < 0 || lastRow < 0) {
primaryList.clear();
secondaryList.clear();
}
// System.out.println("lastRow: " + lastRow + ", first row: " + firstRow + ", original last row: " + e.getLastRow() + ", type: " + e.getType());
List displayedEvents = tableModel.getFilteredEvents();
if (e.getType() == TableModelEvent.INSERT) {
// System.out.println("insert - current warnings: " + warnings.size() + ", errors: " + errors.size() + ", first row: " + firstRow + ", last row: " + lastRow);
for (int i = firstRow; i < lastRow; i++) {
LoggingEventWrapper event = (LoggingEventWrapper) displayedEvents.get(i);
ThumbnailLoggingEventWrapper wrapper = new ThumbnailLoggingEventWrapper(i, event);
if (secondaryMatches(wrapper)) {
secondaryList.add(wrapper);
// System.out.println("added warning: " + i + " - " + event.getLevel());
}
if (primaryMatches(wrapper)) {
//add to this one
primaryList.add(wrapper);
}
// System.out.println("added error: " + i + " - " + event.getLevel());
}
// System.out.println("insert- new warnings: " + warnings + ", errors: " + errors);
//run evaluation on rows & add to list
} else if (e.getType() == TableModelEvent.DELETE) {
//find each eventwrapper with an id in the deleted range and remove it...
// System.out.println("delete- current warnings: " + warnings.size() + ", errors: " + errors.size() + ", first row: " + firstRow + ", last row: " + lastRow + ", displayed event count: " + displayedEvents.size() );
for (Iterator<ThumbnailLoggingEventWrapper> iter = secondaryList.iterator(); iter.hasNext(); ) {
ThumbnailLoggingEventWrapper wrapper = iter.next();
if ((wrapper.rowNum >= firstRow) && (wrapper.rowNum <= lastRow)) {
// System.out.println("deleting find: " + wrapper);
iter.remove();
}
}
for (Iterator<ThumbnailLoggingEventWrapper> iter = primaryList.iterator(); iter.hasNext(); ) {
ThumbnailLoggingEventWrapper wrapper = iter.next();
if ((wrapper.rowNum >= firstRow) && (wrapper.rowNum <= lastRow)) {
// System.out.println("deleting error: " + wrapper);
iter.remove();
}
}
// System.out.println("delete- new warnings: " + warnings.size() + ", errors: " + errors.size());
//remove any matching rows
} else if (e.getType() == TableModelEvent.UPDATE) {
// System.out.println("update - about to delete old warnings in range: " + firstRow + " to " + lastRow + ", current warnings: " + warnings.size() + ", errors: " + errors.size());
//find each eventwrapper with an id in the deleted range and remove it...
for (Iterator<ThumbnailLoggingEventWrapper> iter = secondaryList.iterator(); iter.hasNext(); ) {
ThumbnailLoggingEventWrapper wrapper = iter.next();
if ((wrapper.rowNum >= firstRow) && (wrapper.rowNum <= lastRow)) {
// System.out.println("update - deleting warning: " + wrapper);
iter.remove();
}
}
for (Iterator<ThumbnailLoggingEventWrapper> iter = primaryList.iterator(); iter.hasNext(); ) {
ThumbnailLoggingEventWrapper wrapper = iter.next();
if ((wrapper.rowNum >= firstRow) && (wrapper.rowNum <= lastRow)) {
// System.out.println("update - deleting error: " + wrapper);
iter.remove();
}
}
// System.out.println("update - after deleting old warnings in range: " + firstRow + " to " + lastRow + ", new warnings: " + warnings.size() + ", errors: " + errors.size());
//NOTE: for update, we need to do i<= lastRow
for (int i = firstRow; i <= lastRow; i++) {
LoggingEventWrapper event = (LoggingEventWrapper) displayedEvents.get(i);
ThumbnailLoggingEventWrapper wrapper = new ThumbnailLoggingEventWrapper(i, event);
// System.out.println("update - adding error: " + i + ", event: " + event.getMessage());
//only add event to thumbnail if there is a color
if (primaryMatches(wrapper)) {
//!wrapper.loggingEvent.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND)
primaryList.add(wrapper);
} else {
primaryList.remove(wrapper);
}
if (secondaryMatches(wrapper)) {
//event.isSearchMatch())
// System.out.println("update - adding marker: " + i + ", event: " + event.getMessage());
secondaryList.add(wrapper);
} else {
secondaryList.remove(wrapper);
}
}
// System.out.println("update - new warnings: " + warnings.size() + ", errors: " + errors.size());
}
revalidate();
repaint();
//run this in an invokeLater block to ensure this action is enqueued to the end of the EDT
EventQueue.invokeLater(() -> {
if (isScrollToBottom()) {
scrollToBottom();
}
});
});
}
abstract boolean primaryMatches(ThumbnailLoggingEventWrapper wrapper);
abstract boolean secondaryMatches(ThumbnailLoggingEventWrapper wrapper);
/**
* Get event wrapper - may be null
*
* @param yPosition
* @return event wrapper or null
*/
protected ThumbnailLoggingEventWrapper getEventWrapperAtPosition(int yPosition) {
int rowCount = table.getRowCount();
//'effective' height of this component is scrollpane height
int height = eventsPane.getHeight();
yPosition = Math.max(yPosition, 0);
//don't let clicklocation exceed height
if (yPosition >= height) {
yPosition = height;
}
// System.out.println("clicked y pos: " + e.getPoint().y + ", relative: " + clickLocation);
float ratio = (float) yPosition / height;
int rowToSelect = Math.round(rowCount * ratio);
// System.out.println("rowCount: " + rowCount + ", height: " + height + ", clickLocation: " + clickLocation + ", ratio: " + ratio + ", rowToSelect: " + rowToSelect);
ThumbnailLoggingEventWrapper event = getClosestRow(rowToSelect);
return event;
}
private ThumbnailLoggingEventWrapper getClosestRow(int rowToSelect) {
ThumbnailLoggingEventWrapper closestRow = null;
int rowDelta = Integer.MAX_VALUE;
for (Object aSecondaryList : secondaryList) {
ThumbnailLoggingEventWrapper event = (ThumbnailLoggingEventWrapper) aSecondaryList;
int newRowDelta = Math.abs(rowToSelect - event.rowNum);
if (newRowDelta < rowDelta) {
closestRow = event;
rowDelta = newRowDelta;
}
}
for (Object aPrimaryList : primaryList) {
ThumbnailLoggingEventWrapper event = (ThumbnailLoggingEventWrapper) aPrimaryList;
int newRowDelta = Math.abs(rowToSelect - event.rowNum);
if (newRowDelta < rowDelta) {
closestRow = event;
rowDelta = newRowDelta;
}
}
return closestRow;
}
public Point getToolTipLocation(MouseEvent event) {
//shift tooltip down so the the pointer doesn't cover up events below the current mouse location
return new Point(event.getX(), event.getY() + 30);
}
protected void drawEvent(Color newColor, int verticalLocation, int eventHeight, Graphics g, int x, int width) {
// System.out.println("painting: - color: " + newColor + ", verticalLocation: " + verticalLocation + ", eventHeight: " + eventHeight);
//center drawing at vertical location
int y = verticalLocation + (eventHeight / 2);
Color oldColor = g.getColor();
g.setColor(newColor);
g.fillRect(x, y, width, eventHeight);
if (eventHeight >= 3) {
g.setColor(newColor.darker());
g.drawRect(x, y, width, eventHeight);
}
g.setColor(oldColor);
}
}
class ThumbnailLoggingEventWrapper {
int rowNum;
LoggingEventWrapper loggingEventWrapper;
public ThumbnailLoggingEventWrapper(int rowNum, LoggingEventWrapper loggingEventWrapper) {
this.rowNum = rowNum;
this.loggingEventWrapper = loggingEventWrapper;
}
public String toString() {
return "event - rownum: " + rowNum + ", level: " + loggingEventWrapper.getLoggingEvent().m_level;
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ThumbnailLoggingEventWrapper that = (ThumbnailLoggingEventWrapper) o;
return loggingEventWrapper != null ? loggingEventWrapper.equals(that.loggingEventWrapper) : that.loggingEventWrapper == null;
}
public int hashCode() {
return loggingEventWrapper != null ? loggingEventWrapper.hashCode() : 0;
}
}
class AutoFilterComboBox extends JComboBox {
private boolean bypassFiltering;
private List allEntries = new ArrayList();
private List displayedEntries = new ArrayList();
private AutoFilterComboBoxModel model = new AutoFilterComboBoxModel();
//editor component
private final JTextField textField = new JTextField();
private String lastTextToMatch;
public AutoFilterComboBox() {
textField.setPreferredSize(getPreferredSize());
setModel(model);
setEditor(new AutoFilterEditor());
((JTextField) getEditor().getEditorComponent()).getDocument().addDocumentListener(new AutoFilterDocumentListener());
setEditable(true);
addPopupMenuListener(new PopupMenuListenerImpl());
}
public Vector getModelData() {
//reverse the model order, because it will be un-reversed when we reload it from saved settings
Vector vector = new Vector();
for (Object allEntry : allEntries) {
vector.insertElementAt(allEntry, 0);
}
return vector;
}
private void refilter() {
//only refilter if we're not bypassing filtering AND the text has changed since the last call to refilter
String textToMatch = getEditor().getItem().toString();
if (bypassFiltering || (lastTextToMatch != null && lastTextToMatch.equals(textToMatch))) {
return;
}
lastTextToMatch = textToMatch;
bypassFiltering = true;
model.removeAllElements();
List entriesCopy = new ArrayList(allEntries);
for (Object anEntriesCopy : entriesCopy) {
String thisEntry = anEntriesCopy.toString();
if (thisEntry.toLowerCase(Locale.ENGLISH).contains(textToMatch.toLowerCase())) {
model.addElement(thisEntry);
}
}
bypassFiltering = false;
//TODO: on no-match, don't filter at all (show the popup?)
if (displayedEntries.size() > 0 && !textToMatch.equals("")) {
showPopup();
} else {
hidePopup();
}
}
class AutoFilterEditor implements ComboBoxEditor {
public Component getEditorComponent() {
return textField;
}
public void setItem(Object item) {
if (bypassFiltering) {
return;
}
bypassFiltering = true;
if (item == null) {
textField.setText("");
} else {
textField.setText(item.toString());
}
bypassFiltering = false;
}
public Object getItem() {
return textField.getText();
}
public void selectAll() {
textField.selectAll();
}
public void addActionListener(ActionListener listener) {
textField.addActionListener(listener);
}
public void removeActionListener(ActionListener listener) {
textField.removeActionListener(listener);
}
}
class AutoFilterDocumentListener implements DocumentListener {
public void insertUpdate(DocumentEvent e) {
refilter();
}
public void removeUpdate(DocumentEvent e) {
refilter();
}
public void changedUpdate(DocumentEvent e) {
refilter();
}
}
class AutoFilterComboBoxModel extends AbstractListModel implements MutableComboBoxModel {
private Object selectedItem;
public void addElement(Object obj) {
//assuming add is to displayed list...add to full list (only if not a dup)
bypassFiltering = true;
boolean entryExists = !allEntries.contains(obj);
if (entryExists) {
allEntries.add(obj);
}
displayedEntries.add(obj);
if (!entryExists) {
fireIntervalAdded(this, displayedEntries.size() - 1, displayedEntries.size());
}
bypassFiltering = false;
}
public void removeElement(Object obj) {
int index = displayedEntries.indexOf(obj);
if (index != -1) {
removeElementAt(index);
}
}
public void insertElementAt(Object obj, int index) {
//assuming add is to displayed list...add to full list (only if not a dup)
if (allEntries.contains(obj)) {
return;
}
bypassFiltering = true;
displayedEntries.add(index, obj);
allEntries.add(index, obj);
fireIntervalAdded(this, index, index);
bypassFiltering = false;
refilter();
}
public void removeElementAt(int index) {
bypassFiltering = true;
//assuming removal is from displayed list..remove from full list
Object obj = displayedEntries.get(index);
allEntries.remove(obj);
displayedEntries.remove(obj);
fireIntervalRemoved(this, index, index);
bypassFiltering = false;
refilter();
}
public void setSelectedItem(Object item) {
if ((selectedItem != null && !selectedItem.equals(item)) || selectedItem == null && item != null) {
selectedItem = item;
fireContentsChanged(this, -1, -1);
}
}
public Object getSelectedItem() {
return selectedItem;
}
public int getSize() {
return displayedEntries.size();
}
public Object getElementAt(int index) {
if (index >= 0 && index < displayedEntries.size()) {
return displayedEntries.get(index);
}
return null;
}
public void removeAllElements() {
bypassFiltering = true;
int displayedEntrySize = displayedEntries.size();
if (displayedEntrySize > 0) {
displayedEntries.clear();
//if firecontentschaned is used, the combobox resizes..use fireintervalremoved instead, which doesn't do that..
fireIntervalRemoved(this, 0, displayedEntrySize - 1);
}
bypassFiltering = false;
}
public void showAllElements() {
//first remove whatever is there and fire necessary events then add events
removeAllElements();
bypassFiltering = true;
displayedEntries.addAll(allEntries);
if (displayedEntries.size() > 0) {
fireIntervalAdded(this, 0, displayedEntries.size() - 1);
}
bypassFiltering = false;
}
}
private class PopupMenuListenerImpl implements PopupMenuListener {
private boolean willBecomeVisible = false;
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
bypassFiltering = true;
((JComboBox) e.getSource()).setSelectedIndex(-1);
bypassFiltering = false;
if (!willBecomeVisible) {
//we already have a match but we're showing the popup - unfilter
if (displayedEntries.contains(textField.getText())) {
model.showAllElements();
}
//workaround for bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4743225
//the height of the popup after updating entries in this listener was not updated..
JComboBox list = (JComboBox) e.getSource();
willBecomeVisible = true; // the flag is needed to prevent a loop
try {
list.getUI().setPopupVisible(list, true);
} finally {
willBecomeVisible = false;
}
}
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
//no-op
}
public void popupMenuCanceled(PopupMenuEvent e) {
//no-op
}
}
}
class ToggleToolTips extends JCheckBoxMenuItem {
public ToggleToolTips() {
super("Show ToolTips", new ImageIcon(ChainsawIcons.TOOL_TIP));
addActionListener(
evt -> preferenceModel.setToolTips(isSelected()));
}
}
}