/*
* Created by JFormDesigner on Tue Feb 12 15:26:54 GMT 2013
*/
package net.cakenet.jsaton.ui.tools;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.HTML;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
/**
* @author James Lawrence
*/
public class LogPanel extends JPanel {
private static final int LOG_LENGTH = 3000;
private final Object docLock = new Object(); // lock on modifying the document
private AttributeSet inAttrs; // attributes used for user input
private LogInputStream in = new LogInputStream(); // an input stream for the user input (updated on newline)
private StyledDocument doc; // our document
private String input; // the input string (buffer)
private int outOff; // our offset into the document for output
private transient boolean output; // whether the current write/delete operation is for output
// hyperlink shit... (bad to put it here, I know)
private int hyperlinkOff = -1;
private int hyperlinkEndCount;
private boolean hyperlinkStart, inHyperlink;
private String hyperlink;
private java.util.List<Character> validChars = Arrays.asList('/', '%', '.', '\\', '_', '&', '#', ';', '+', '=',
'-', ':', ' ');
public LogPanel() {
initComponents();
this.doc = (StyledDocument) messages.getDocument();
// This filter makes sure that input text is styled and that the output isn't modifiable
((AbstractDocument) doc).setDocumentFilter(new DocumentFilter() {
private void updateInput() throws BadLocationException {
input = doc.getText(outOff, doc.getLength() - outOff);
if (input.isEmpty())
input = null;
}
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
final boolean in = !output;
if (in) {
if (offset < outOff) {
length -= outOff - offset;
offset = outOff;
}
}
super.remove(fb, offset, length);
if (in && length != 0)
updateInput();
}
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
final boolean in = !output;
if (in) {
if (offset < outOff) {
length -= outOff - offset;
offset = outOff;
}
}
super.replace(fb, offset, length, text, inAttrs);
if (in) {
updateInput();
if (text.contains("\n")) {
synchronized (docLock) {
// New line, we need to push the input!
int idx = input.lastIndexOf('\n');
String toPush = input.substring(0, idx + 1); // inlclude the newline
outOff += toPush.length();
updateInput();
synchronized (LogPanel.this.in.buffer) {
for (char c : toPush.toCharArray())
LogPanel.this.in.buffer.add(c);
}
}
}
}
}
});
SimpleAttributeSet sas = new SimpleAttributeSet();
StyleConstants.setForeground(sas, Color.GREEN);
inAttrs = sas;
doc.setCharacterAttributes(0, 1, inAttrs, false);
messages.setEditable(true);
}
private void _write(String s, AttributeSet attrs, boolean in) {
synchronized (docLock) {
output = !in;
int startOff = -1;
try {
startOff = outOff;
int caretPos = messages.getCaretPosition();
doc.insertString(outOff, s, attrs);
if (output)
outOff += s.length();
// If the element before the one we're inserting has the same attributes as us, change the range...
if (startOff != 0) {
Element prev = doc.getCharacterElement(startOff);
AttributeSet prevAttrs = prev.getAttributes();
if (prevAttrs.getAttributeCount() == attrs.getAttributeCount() && prevAttrs.containsAttributes(attrs)) {
doc.setCharacterAttributes(startOff, outOff - startOff, attrs, true); // merge...
}
}
if (caretPos >= startOff)
messages.setCaretPosition(outOff);
} catch (BadLocationException e) {
e.printStackTrace();
}
// Nasty hyperlink stuff...
char[] charArray = s.toCharArray();
for (int i1 = 0; i1 < charArray.length; i1++) {
int off = startOff + i1 + 1;
char b = charArray[i1];
if (inHyperlink) {
boolean valid = Character.isLetterOrDigit(b);
valid |= validChars.contains(b);
if (valid) {
hyperlink += b;
} else {
inHyperlink = false;
SimpleAttributeSet hyperlinkAttrs = new SimpleAttributeSet();
StyleConstants.setForeground(hyperlinkAttrs, Color.BLUE);
StyleConstants.setUnderline(hyperlinkAttrs, true);
hyperlinkAttrs.addAttribute(HTML.Attribute.HREF, hyperlink);
doc.setCharacterAttributes(off - (hyperlink.length() + 1), hyperlink.length(), hyperlinkAttrs, true);
}
}
if (b == ':') {
hyperlinkStart = true;
hyperlinkOff = off - 1;
} else if (hyperlinkStart && b == '/') {
hyperlinkEndCount++;
if (hyperlinkEndCount == 2) {
try {
String text = doc.getText(0, hyperlinkOff);
String proto = "";
for (int i = text.length() - 1; i >= 0; i--) {
char c = text.charAt(i);
if (!Character.isLetter(c))
break;
proto = c + proto;
hyperlinkOff = i;
}
hyperlink = proto + "://";
inHyperlink = true;
} catch (Exception e) {
e.printStackTrace();
}
hyperlinkEndCount = 0;
hyperlinkOff = off - 1;
hyperlinkStart = false;
}
} else {
hyperlinkOff = -1;
hyperlinkEndCount = 0;
hyperlinkStart = false;
}
}
}
}
public synchronized void write(String s, AttributeSet attrs) {
_write(s, attrs, false);
synchronized (docLock) {
final int inputLen = input == null ? 0 : input.length();
final int toDelete = (outOff - inputLen) - LOG_LENGTH;
if (toDelete > 0) {
try {
int idx = doc.getText(0, toDelete).indexOf('\n') + 1;
if (idx != 0) {
doc.remove(0, idx);
outOff -= idx;
}
} catch (BadLocationException e) {
/**/
}
}
}
output = false;
}
public LogOutputStream createOutputStream(OutputStream delegate, AttributeSet attrs) {
return new LogOutputStream(delegate, attrs);
}
public LogInputStream getInputStream() {
return in;
}
public void setInputStreamAttributes(AttributeSet attrs) {
inAttrs = attrs;
}
private void messagesMouseMoved(MouseEvent e) {
getLink(e.getPoint());
}
private void messagesMouseReleased(MouseEvent e) {
}
private void messagesMouseClicked(MouseEvent e) {
String link = getLink(e.getPoint());
if (link == null)
return;
try {
if (!Desktop.isDesktopSupported()) {
System.err.println("Unable to open link, your OS doesn't support java desktop interaction");
return;
}
if (link.startsWith("http://") || link.startsWith("https://")) {
URL url = new URL(link);
Desktop.getDesktop().browse(url.toURI());
} else {
int protIdx = link.indexOf(":");
String protocol = link.substring(0, protIdx);
String path = link.substring(protIdx + 3);
if (protocol.equals("file")) {
File f = new File(path);
Desktop.getDesktop().open(f);
} else {
System.out.println("Unhandled protocol: " + protocol);
}
}
} catch (IOException | URISyntaxException e1) {
e1.printStackTrace();
}
}
Cursor prevCursor;
private String getLink(Point p) {
int pos = messages.viewToModel(p);
Element at = doc.getCharacterElement(pos);
AttributeSet attrs = at.getAttributes();
Object href = attrs.getAttribute(HTML.Attribute.HREF);
if (href != null && prevCursor == null) {
prevCursor = messages.getCursor();
messages.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else if (href == null && prevCursor != null) {
messages.setCursor(prevCursor);
prevCursor = null;
}
if (href != null) {
return (String) href;
}
return null;
}
public class LogOutputStream extends OutputStream {
private OutputStream delegate;
private AttributeSet attributes;
public LogOutputStream(OutputStream delegate, AttributeSet attrs) {
this.delegate = delegate;
this.attributes = attrs;
}
public void write(byte[] b, int off, int len) throws IOException {
LogPanel.this.write(new String(b, off, len), attributes);
if (delegate != null)
delegate.write(b, off, len);
}
public void write(int b) throws IOException {
LogPanel.this.write(new String(new byte[]{(byte) b}), attributes);
if (delegate != null)
delegate.write(b);
}
}
public class LogInputStream extends InputStream {
private final Queue<Character> buffer = new LinkedList<>();
public int read(byte[] b, int off, int len) throws IOException {
int toRead = Math.min(available(), len);
for (int i = 0; i < toRead; i++)
b[off + i] = (byte) read();
return toRead;
}
public int read() throws IOException {
boolean empty;
synchronized (buffer) {
empty = buffer.isEmpty();
if (empty) {
try {
buffer.wait();
} catch (InterruptedException e) {
/**/
}
}
return buffer.poll() & 0xff;
}
}
public int available() throws IOException {
return buffer.size();
}
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
scrollPanel = new JScrollPane();
noWrapPanel = new JPanel();
messages = new JTextPane();
commands = new JPanel();
button1 = new JButton();
button2 = new JButton();
wrapToggle = new JToggleButton();
clearAction = new ClearAction();
toggleWordwrapAction = new ToggleWordWrapAction();
scrollToBottomAction = new ScrollToBottomAction();
//======== this ========
setLayout(new BorderLayout());
//======== scrollPanel ========
{
//======== noWrapPanel ========
{
noWrapPanel.setLayout(new BorderLayout());
//---- messages ----
messages.setEditable(false);
messages.setContentType("text/rtf");
messages.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
messagesMouseMoved(e);
}
});
messages.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
messagesMouseClicked(e);
}
@Override
public void mouseReleased(MouseEvent e) {
messagesMouseReleased(e);
}
});
noWrapPanel.add(messages, BorderLayout.CENTER);
}
scrollPanel.setViewportView(noWrapPanel);
}
add(scrollPanel, BorderLayout.CENTER);
//======== commands ========
{
commands.setLayout(new BoxLayout(commands, BoxLayout.Y_AXIS));
//---- button1 ----
button1.setAction(clearAction);
button1.setHideActionText(true);
button1.setMargin(new Insets(2, 2, 2, 2));
commands.add(button1);
//---- button2 ----
button2.setMargin(new Insets(2, 2, 2, 2));
button2.setAction(scrollToBottomAction);
button2.setHideActionText(true);
commands.add(button2);
//---- wrapToggle ----
wrapToggle.setAction(toggleWordwrapAction);
wrapToggle.setHideActionText(true);
wrapToggle.setMargin(new Insets(2, 2, 2, 2));
commands.add(wrapToggle);
}
add(commands, BorderLayout.WEST);
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JScrollPane scrollPanel;
private JPanel noWrapPanel;
private JTextPane messages;
private JPanel commands;
private JButton button1;
private JButton button2;
private JToggleButton wrapToggle;
private ClearAction clearAction;
private ToggleWordWrapAction toggleWordwrapAction;
private ScrollToBottomAction scrollToBottomAction;
// JFormDesigner - End of variables declaration //GEN-END:variables
private class ClearAction extends AbstractAction {
private ClearAction() {
// JFormDesigner - Action initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
putValue(NAME, "Clear");
putValue(SHORT_DESCRIPTION, "Clear the log");
putValue(SMALL_ICON, new ImageIcon(getClass().getResource("/icons/delete.png")));
// JFormDesigner - End of action initialization //GEN-END:initComponents
}
public void actionPerformed(ActionEvent e) {
synchronized (docLock) {
output = true; // Only way this will work...
try {
doc.remove(0, outOff);
} catch (BadLocationException e1) {
/**/
}
output = false;
outOff = 0;
}
}
}
private class ToggleWordWrapAction extends AbstractAction {
private ToggleWordWrapAction() {
// JFormDesigner - Action initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
putValue(NAME, "Toggle wordwrap");
putValue(SHORT_DESCRIPTION, "Toggle word wrap in the log");
putValue(SMALL_ICON, new ImageIcon(getClass().getResource("/icons/doc_resize_actual.png")));
// JFormDesigner - End of action initialization //GEN-END:initComponents
}
public void actionPerformed(ActionEvent e) {
if (wrapToggle.isSelected()) {
noWrapPanel.remove(messages);
scrollPanel.setViewportView(messages);
} else {
noWrapPanel.add(messages);
scrollPanel.setViewportView(noWrapPanel);
}
}
}
private class ScrollToBottomAction extends AbstractAction {
private ScrollToBottomAction() {
// JFormDesigner - Action initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
putValue(NAME, "Scroll to bottom");
putValue(SHORT_DESCRIPTION, "Scroll to the bottom");
putValue(SMALL_ICON, new ImageIcon(getClass().getResource("/icons/application_put.png")));
// JFormDesigner - End of action initialization //GEN-END:initComponents
}
public void actionPerformed(ActionEvent e) {
messages.setCaretPosition(doc.getLength());
}
}
}