/*
* LogViewer.java
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 1999, 2004 Slava Pestov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.gui;
//{{{ Imports
import java.awt.*;
import java.awt.event.*;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;
import org.gjt.sp.jedit.*;
import org.gjt.sp.jedit.EditBus.EBHandler;
import org.gjt.sp.jedit.msg.PropertiesChanged;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.ThreadUtilities;
//}}}
/** Activity Log Viewer
* @version $Id$
*/
public class LogViewer extends JPanel implements DefaultFocusComponent
{
private final ColorizerCellRenderer cellRenderer;
//{{{ LogViewer constructor
@SuppressWarnings({"unchecked"}) // The FilteredListModel needs work
public LogViewer()
{
super(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 0));
JPanel caption = new JPanel();
caption.setLayout(new BoxLayout(caption,BoxLayout.X_AXIS));
caption.setBorder(new EmptyBorder(6, 0, 6, 0));
String settingsDirectory = jEdit.getSettingsDirectory();
if(settingsDirectory != null)
{
String[] args = { MiscUtilities.constructPath(
settingsDirectory, "activity.log") };
JLabel label = new JLabel(jEdit.getProperty(
"log-viewer.caption",args));
caption.add(label);
}
caption.add(Box.createHorizontalGlue());
tailIsOn = jEdit.getBooleanProperty("log-viewer.tail", false);
tail = new JCheckBox(
jEdit.getProperty("log-viewer.tail.label"),tailIsOn);
tail.addActionListener(new ActionHandler());
filter = new JTextField();
filter.getDocument().addDocumentListener(new DocumentListener()
{
@Override
public void changedUpdate(DocumentEvent e)
{
setFilter();
}
@Override
public void insertUpdate(DocumentEvent e)
{
setFilter();
}
@Override
public void removeUpdate(DocumentEvent e)
{
setFilter();
}
});
caption.add(filter);
caption.add(tail);
caption.add(Box.createHorizontalStrut(12));
copy = new JButton(jEdit.getProperty("log-viewer.copy"));
copy.addActionListener(new ActionHandler());
caption.add(copy);
caption.add(Box.createHorizontalStrut(6));
JButton settings = new JButton(jEdit.getProperty("log-viewer.settings.label"));
settings.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ae)
{
new LogSettings();
}
});
caption.add(settings);
ListModel model = Log.getLogListModel();
listModel = new MyFilteredListModel(model);
// without this, listModel is held permanently in model.
// See addNotify() and removeNotify(), and constructor of
// FilteredListModel.
model.removeListDataListener(listModel);
list = new LogList(listModel);
listModel.setList(list);
cellRenderer = new ColorizerCellRenderer();
list.setCellRenderer(cellRenderer);
setFilter();
add(BorderLayout.NORTH,caption);
JScrollPane scroller = new JScrollPane(list);
Dimension dim = scroller.getPreferredSize();
dim.width = Math.min(600,dim.width);
scroller.setPreferredSize(dim);
add(BorderLayout.CENTER,scroller);
propertiesChanged();
} //}}}
//{{{ setBounds() method
@Override
public void setBounds(int x, int y, int width, int height)
{
super.setBounds(x, y, width, height);
scrollLaterIfRequired();
} //}}}
//{{{ handlePropertiesChanged() method
@EBHandler
public void handlePropertiesChanged(PropertiesChanged msg)
{
propertiesChanged();
} //}}}
//{{{ addNotify() method
@Override
public void addNotify()
{
super.addNotify();
cellRenderer.updateColors();
ListModel model = Log.getLogListModel();
model.addListDataListener(listModel);
model.addListDataListener(listHandler = new ListHandler());
if(tailIsOn)
scrollToTail();
EditBus.addToBus(this);
} //}}}
//{{{ removeNotify() method
@Override
public void removeNotify()
{
super.removeNotify();
ListModel model = Log.getLogListModel();
model.removeListDataListener(listModel);
model.removeListDataListener(listHandler);
listHandler = null;
EditBus.removeFromBus(this);
} //}}}
//{{{ focusOnDefaultComponent() method
@Override
public void focusOnDefaultComponent()
{
list.requestFocus();
} //}}}
//{{{ Private members
private ListHandler listHandler;
private final FilteredListModel<ListModel<String>> listModel;
private final JList list;
private final JButton copy;
private final JCheckBox tail;
private final JTextField filter;
private boolean tailIsOn;
private static boolean showDebug = jEdit.getBooleanProperty("log-viewer.message.debug", true);
private static boolean showMessage = jEdit.getBooleanProperty("log-viewer.message.message", true);
private static boolean showNotice = jEdit.getBooleanProperty("log-viewer.message.notice", true);
private static boolean showWarning = jEdit.getBooleanProperty("log-viewer.message.warning", true);
private static boolean showError = jEdit.getBooleanProperty("log-viewer.message.error", true);
//{{{ setFilter() method
private void setFilter()
{
String toFilter = filter.getText();
listModel.setFilter(toFilter.length() == 0 ? " " : toFilter);
scrollLaterIfRequired();
} //}}}
//{{{ propertiesChanged() method
private void propertiesChanged()
{
cellRenderer.updateColors();
list.setFont(jEdit.getFontProperty("view.font"));
list.setFixedCellHeight(list.getFontMetrics(list.getFont())
.getHeight());
} //}}}
//{{{ scrollToTail() method
/** Scroll to the tail of the logs. */
private void scrollToTail()
{
int index = list.getModel().getSize();
if(index != 0)
list.ensureIndexIsVisible(index - 1);
} //}}}
//{{{ scrollLaterIfRequired() method
private void scrollLaterIfRequired()
{
if (tailIsOn)
ThreadUtilities.runInDispatchThread(new Runnable()
{
@Override
public void run()
{
scrollToTail();
}
});
} //}}}
//}}}
//{{{ ActionHandler class
@SuppressWarnings({"deprecation"}) // see note below
private class ActionHandler implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e)
{
Object src = e.getSource();
if(src == tail)
{
tailIsOn = !tailIsOn;
jEdit.setBooleanProperty("log-viewer.tail",tailIsOn);
if(tailIsOn)
{
scrollToTail();
}
}
else if(src == copy)
{
StringBuilder buf = new StringBuilder();
// TODO: list.getSelectedValues is deprecated. Need to finish the
// conversion to generics for this class at some point.
Object[] selected = list.getSelectedValues();
if(selected != null && selected.length != 0)
{
for (Object sel : selected)
{
buf.append(sel);
buf.append('\n');
}
}
else
{
ListModel model = list.getModel();
for(int i = 0; i < model.getSize(); i++)
{
buf.append(model.getElementAt(i));
buf.append('\n');
}
}
Registers.setRegister('$',buf.toString());
}
}
} //}}}
//{{{ ListHandler class
private class ListHandler implements ListDataListener
{
@Override
public void intervalAdded(ListDataEvent e)
{
contentsChanged(e);
}
@Override
public void intervalRemoved(ListDataEvent e)
{
contentsChanged(e);
}
@Override
public void contentsChanged(ListDataEvent e)
{
scrollLaterIfRequired();
}
} //}}}
//{{{ LogList class
@SuppressWarnings({"unchecked"}) // The FilteredListModel needs work
private class LogList extends JList
{
LogList(ListModel model)
{
super(model);
setVisibleRowCount(24);
getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
setAutoscrolls(true);
}
@Override
public void processMouseEvent(MouseEvent evt)
{
if(evt.getID() == MouseEvent.MOUSE_PRESSED)
{
startIndex = list.locationToIndex(evt.getPoint());
}
super.processMouseEvent(evt);
}
@Override
public void processMouseMotionEvent(MouseEvent evt)
{
if(evt.getID() == MouseEvent.MOUSE_DRAGGED)
{
int row = list.locationToIndex(evt.getPoint());
if(row != -1)
{
if(startIndex == -1)
{
list.setSelectionInterval(row,row);
startIndex = row;
}
else
list.setSelectionInterval(startIndex,row);
list.ensureIndexIsVisible(row);
evt.consume();
}
}
else
super.processMouseMotionEvent(evt);
}
private int startIndex;
} //}}}
//{{{ ColorizerCellRenderer class
private static class ColorizerCellRenderer extends JLabel implements ListCellRenderer
{
private static Color debugColor;
private static Color messageColor;
private static Color noticeColor;
private static Color warningColor;
private static Color errorColor;
private ColorizerCellRenderer()
{
updateColors();
}
// This is the only method defined by ListCellRenderer.
// We just reconfigure the JLabel each time we're called.
@Override
public Component getListCellRendererComponent(
JList list,
Object value, // value to display
int index, // cell index
boolean isSelected, // is the cell selected
boolean cellHasFocus ) // the list and the cell have the focus
{
String s = value.toString();
setText(s);
if (isSelected)
{
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else
{
setBackground(list.getBackground());
Color color = list.getForeground();
if (s.contains("[debug]"))
{
color = debugColor;
}
else if (s.contains("[message]"))
{
color = messageColor;
}
else if (s.contains("[notice]"))
{
color = noticeColor;
}
else if (s.contains("[warning]"))
{
color = warningColor;
}
else if (s.contains("[error]"))
{
color = errorColor;
}
setForeground(color);
}
setEnabled(list.isEnabled());
setFont(list.getFont());
setOpaque(true);
return this;
}
public void updateColors()
{
debugColor = jEdit.getColorProperty("log-viewer.message.debug.color", Color.BLUE);
messageColor = jEdit.getColorProperty("log-viewer.message.message.color", Color.BLACK);
noticeColor = jEdit.getColorProperty("log-viewer.message.notice.color", Color.GREEN);
warningColor = jEdit.getColorProperty("log-viewer.message.warning.color", Color.ORANGE);
errorColor = jEdit.getColorProperty("log-viewer.message.error.color", Color.RED);
}
} //}}}
//{{{ MyFilteredListModel
private static class MyFilteredListModel extends FilteredListModel<ListModel<String>>
{
MyFilteredListModel(ListModel<String> model)
{
super(model);
}
@Override
public String prepareFilter(String filter)
{
return filter.toLowerCase();
}
@Override
public boolean passFilter(int row, @Nullable String filter)
{
if (filter == null || filter.isEmpty())
return true;
String text = delegated.getElementAt(row).toString().toLowerCase();
if (!showDebug && text.contains("[debug]"))
return false;
if (!showMessage && text.contains("[message]"))
return false;
if (!showNotice && text.contains("[notice]"))
return false;
if (!showWarning && text.contains("[warning]"))
return false;
if (!showError && text.contains("[error]"))
return false;
return text.contains(filter);
}
} //}}}
//{{{ LogSettings dialog
private class LogSettings extends JDialog
{
LogSettings()
{
super(jEdit.getActiveView(), jEdit.getProperty("log-viewer.dialog.title"));
AbstractOptionPane pane = new AbstractOptionPane(jEdit.getProperty("log-viewer.settings.label"))
{
@Override
protected void _init()
{
setBorder(BorderFactory.createEmptyBorder(11, 11, 12, 12));
maxLines = new JSpinner(new SpinnerNumberModel(jEdit.getIntegerProperty("log-viewer.maxlines", 500), 500, Integer.MAX_VALUE, 1));
addComponent(jEdit.getProperty("log-viewer.maxlines.label", "Max lines"),
maxLines,
GridBagConstraints.REMAINDER);
addComponent(Box.createVerticalStrut(11));
debug = new JCheckBox(jEdit.getProperty("log-viewer.message.debug.label", "Debug"),
jEdit.getBooleanProperty("log-viewer.message.debug", true));
message = new JCheckBox(jEdit.getProperty("log-viewer.message.message.label", "Message"),
jEdit.getBooleanProperty("log-viewer.message.message", true));
notice = new JCheckBox(jEdit.getProperty("log-viewer.message.notice.label", "Notice"),
jEdit.getBooleanProperty("log-viewer.message.notice", true));
warning = new JCheckBox(jEdit.getProperty("log-viewer.message.warning.label", "Warning"),
jEdit.getBooleanProperty("log-viewer.message.warning", true));
error = new JCheckBox(jEdit.getProperty("log-viewer.message.error.label", "Error"),
jEdit.getBooleanProperty("log-viewer.message.error", true));
addComponent(new JLabel(jEdit.getProperty("log-viewer.message.label", "Message Display:")));
addComponent(debug,
debugColor = new ColorWellButton(
jEdit.getColorProperty("log-viewer.message.debug.color", Color.BLUE)),
GridBagConstraints.REMAINDER);
addComponent(message,
messageColor = new ColorWellButton(
jEdit.getColorProperty("log-viewer.message.message.color", Color.GREEN)),
GridBagConstraints.REMAINDER);
addComponent(notice,
noticeColor = new ColorWellButton(
jEdit.getColorProperty("log-viewer.message.notice.color", Color.GREEN)),
GridBagConstraints.REMAINDER);
addComponent(warning,
warningColor = new ColorWellButton(
jEdit.getColorProperty("log-viewer.message.warning.color", Color.ORANGE)),
GridBagConstraints.REMAINDER);
addComponent(error,
errorColor = new ColorWellButton(
jEdit.getColorProperty("log-viewer.message.error.color", Color.RED)),
GridBagConstraints.REMAINDER);
addComponent(Box.createVerticalStrut(11));
beep = new JCheckBox(jEdit.getProperty("debug.beepOnOutput.label"),
jEdit.getBooleanProperty("debug.beepOnOutput", false));
addComponent(beep);
addComponent(Box.createVerticalStrut(11));
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
JButton okButton = new JButton(jEdit.getProperty("common.ok"));
okButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ae)
{
save();
LogSettings.this.setVisible(false);
LogSettings.this.dispose();
}
});
JButton cancelButton = new JButton(jEdit.getProperty("common.cancel"));
cancelButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ae)
{
LogSettings.this.setVisible(false);
LogSettings.this.dispose();
}
});
buttonPanel.add(okButton);
buttonPanel.add(cancelButton);
addComponent(buttonPanel, GridBagConstraints.HORIZONTAL);
}
@Override
protected void _save()
{
jEdit.setIntegerProperty("log-viewer.maxlines", ((SpinnerNumberModel)maxLines.getModel()).getNumber().intValue());
showDebug = debug.isSelected();
jEdit.setBooleanProperty("log-viewer.message.debug", showDebug);
showMessage = message.isSelected();
jEdit.setBooleanProperty("log-viewer.message.message", showMessage);
showNotice = notice.isSelected();
jEdit.setBooleanProperty("log-viewer.message.notice", showNotice);
showWarning = warning.isSelected();
jEdit.setBooleanProperty("log-viewer.message.warning", showWarning);
showError = error.isSelected();
jEdit.setBooleanProperty("log-viewer.message.error", showError);
jEdit.setColorProperty("log-viewer.message.debug.color", debugColor.getSelectedColor());
jEdit.setColorProperty("log-viewer.message.message.color", messageColor.getSelectedColor());
jEdit.setColorProperty("log-viewer.message.notice.color", noticeColor.getSelectedColor());
jEdit.setColorProperty("log-viewer.message.warning.color", warningColor.getSelectedColor());
jEdit.setColorProperty("log-viewer.message.error.color", errorColor.getSelectedColor());
jEdit.setBooleanProperty("debug.beepOnOutput", beep.isSelected());
setFilter();
// it would be most clean to call jEdit.propertiesChanged() now
// which is needed since global debug.beepOnOutput flag is attached to this pane;
// but to avoid extra log entries, we workaround it by direct Log access
Log.setBeepOnOutput(beep.isSelected());
// jEdit.propertiesChanged();
}
};
setContentPane(pane);
pane.init();
pack();
setLocationRelativeTo(LogViewer.this);
setVisible(true);
}
private JSpinner maxLines;
private JCheckBox debug;
private JCheckBox message;
private JCheckBox notice;
private JCheckBox warning;
private JCheckBox error;
private ColorWellButton debugColor;
private ColorWellButton messageColor;
private ColorWellButton noticeColor;
private ColorWellButton warningColor;
private ColorWellButton errorColor;
private JCheckBox beep;
} //}}}
}