/**
* Copyright 2014
* SMEdit https://github.com/StarMade/SMEdit
* SMTools https://github.com/StarMade/SMTools
*
* Licensed 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 jo.sm.ui.component;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.swing.AbstractListModel;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JTextPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import jo.log.LogFormatter;
import jo.util.StringUtil;
/**
* @Auther Robert Barefoot for SMEdit Classic - version 1.0
**/
public class LogTextArea extends JList {
public static final int MAX_ENTRIES = 100;
public static final Rectangle BOTTOM_OF_WINDOW = new Rectangle(0,
Integer.MAX_VALUE, 0, 0);
private static final long serialVersionUID = 568094664651684086L;
private static final Formatter formatter = new Formatter() {
private final SimpleDateFormat dateFormat = new SimpleDateFormat(
"hh:mm:ss");
@Override
public String format(final LogRecord record) {
final String[] className = record.getLoggerName().split("\\.");
final String name = className[className.length - 1];
final int maxLen = 22;
final String append = "...";
return String.format(
"[%s] %-" + maxLen + "s %s %s",
dateFormat.format(record.getMillis()),
name.length() > maxLen ? name.substring(0,
maxLen - append.length())
+ append : name, record.getMessage(),
StringUtil.throwableToString(record.getThrown()));
}
};
private static final Formatter copyPasteFormatter = new LogFormatter(false);
private static final Logger LOG = Logger.getLogger(LogTextArea.class.getName());
private final LogQueue logQueue = new LogQueue();
private final LogAreaListModel model = new LogAreaListModel();
private final Runnable scrollToBottom = new Runnable() {
@Override
public void run() {
scrollRectToVisible(LogTextArea.BOTTOM_OF_WINDOW);
}
};
@SuppressWarnings("unchecked")
public LogTextArea() {
setModel(model);
setCellRenderer(new Renderer());
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
String fontName = Font.MONOSPACED;
for (final Font font : GraphicsEnvironment
.getLocalGraphicsEnvironment().getAllFonts()) {
final String name = font.getName();
if (name.matches("Monaco|Consolas")) {
fontName = name;
break;
}
}
setFont(new Font(fontName, Font.PLAIN, 12));
new Thread(logQueue, "LogGuiQueue").start();
}
/**
* Logs a new entry to be shown in the list. Thread safe.
*
* @param logRecord
* The entry.
*/
public void log(final LogRecord logRecord) {
logQueue.queue(new WrappedLogRecord(logRecord));
}
private static class Renderer implements ListCellRenderer {
private final Border EMPTY_BORDER = new EmptyBorder(1, 1, 1, 1);
private final Border SELECTED_BORDER = UIManager
.getBorder("List.focusCellHighlightBorder");
private final Color DARK_GREEN = new Color(0, 0xcc, 0);
private final Color DARK_RED = new Color(0xcc, 0, 0);
private final Color DARK_PURPLE = new Color(0xcc, 0, 0xcc);
private final Color DARK_BLUE = new Color(0, 0, 0xcc);
private final Color GREY = new Color(0xcc, 0xcc, 0xcc);
@Override
public Component getListCellRendererComponent(final JList list,
final Object value, final int index, final boolean isSelected,
final boolean cellHasFocus) {
if (!(value instanceof WrappedLogRecord)) {
return new JLabel();
}
final WrappedLogRecord wlr = (WrappedLogRecord) value;
final JTextPane result = new JTextPane();
result.setDragEnabled(true);
result.setText(wlr.formatted);
result.setComponentOrientation(list.getComponentOrientation());
result.setFont(list.getFont());
result.setBorder(cellHasFocus || isSelected ? SELECTED_BORDER
: EMPTY_BORDER);
if (wlr.record.getLevel() == Level.CONFIG) {
result.setForeground(DARK_BLUE);
}
if (wlr.record.getLevel() == Level.SEVERE) {
result.setBackground(DARK_RED);
result.setForeground(Color.WHITE);
}
if (wlr.record.getLevel() == Level.WARNING) {
result.setForeground(DARK_RED);
}
if (wlr.record.getLevel() == Level.FINE
|| wlr.record.getLevel() == Level.FINER
|| wlr.record.getLevel() == Level.FINEST) {
result.setForeground(DARK_GREEN);
}
final Object[] parameters = wlr.record.getParameters();
if (parameters != null) {
for (final Object parameter : parameters) {
if (parameter == null) {
continue;
}
if (parameter instanceof Color) {
result.setForeground((Color) parameter);
} else if (parameter instanceof Font) {
result.setFont((Font) parameter);
}
}
}
return result;
}
}
private class LogAreaListModel extends AbstractListModel {
private static final long serialVersionUID = 0;
private List<WrappedLogRecord> records = new ArrayList<>(
LogTextArea.MAX_ENTRIES);
public void addAllElements(final List<WrappedLogRecord> obj) {
records.addAll(obj);
if (getSize() > LogTextArea.MAX_ENTRIES) {
records = records.subList(
(getSize() - LogTextArea.MAX_ENTRIES), getSize());
fireContentsChanged(this, 0, (getSize() - 1));
} else {
fireIntervalAdded(this, (getSize() - 1), (getSize() - 1));
}
}
@Override
public Object getElementAt(final int index) {
return records.get(index);
}
@Override
public int getSize() {
return records.size();
}
}
/**
* Flushes every #FLUSH_RATE (milliseconds)
*/
private class LogQueue implements Runnable {
public static final int FLUSH_RATE = 1000;
private final Object lock = new Object();
private List<WrappedLogRecord> queue = new ArrayList<>(
100);
public void queue(final WrappedLogRecord record) {
synchronized (lock) {
queue.add(record);
}
}
@Override
public void run() {
while (true) {
List<WrappedLogRecord> toFlush = null;
synchronized (lock) {
if (!queue.isEmpty()) {
toFlush = new ArrayList<>(queue);
queue = queue.subList(0, 0);
}
}
if (toFlush != null) { // Hold the lock for as little time as
// possible
model.addAllElements(toFlush);
SwingUtilities.invokeLater(scrollToBottom);
}
try {
Thread.sleep(LogQueue.FLUSH_RATE);
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* Wrap the log records so we can control the copy paste text (via
* #toString)
*/
private class WrappedLogRecord {
public final LogRecord record;
public final String formatted;
WrappedLogRecord(final LogRecord record) {
this.record = record;
formatted = LogTextArea.formatter.format(record);
}
@Override
public String toString() {
return LogTextArea.copyPasteFormatter.format(record);
}
}
}