2016-10-02 18:10:14 +02:00

334 lines
11 KiB
Java

package lu.jpt.csparqltest.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collection;
import java.util.Iterator;
import java.util.Observable;
import java.util.Observer;
import java.util.StringTokenizer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import eu.larkc.csparql.cep.api.RdfQuadruple;
import eu.larkc.csparql.common.RDFTable;
import eu.larkc.csparql.common.RDFTuple;
import eu.larkc.csparql.core.engine.CsparqlQueryResultProxy;
/**
* Multi-purpose text window to allow for easy viewing of RdfStream quadruples,
* various logging or observing query results.
*/
public class TextObserverWindow extends JFrame implements Observer {
private static final long serialVersionUID = 1L;
private static final int TEXTPANE_WIDTH = 800;
private static final int TEXTPANE_HEIGHT = 240;
private JTextPane textPane;
private volatile boolean autoScrollToBottom = true;
/**
* Constructor
* @param title of the window
*/
public TextObserverWindow(String title) {
this.initWindow(title);
}
/**
* Set whether the window is automatically scrolling to the bottom
* @param autoScrollToBottom
*/
public void setAutoScrollToBottom(boolean autoScrollToBottom) {
this.autoScrollToBottom = autoScrollToBottom;
}
/**
* Show the given text in the window
* @param text
*/
public void showText(String text) {
this.showText(text, Color.BLACK);
}
/**
* Show given text in the window using the given color
* @param text
* @param color
*/
public void showText(String text, Color color) {
this.appendToTextPane(text, true, color);
}
/**
* Show the given token in the window without appending linebreaks
* @param text
*/
public void showToken(String token) {
this.showText(token, Color.BLACK);
}
/**
* Show given token in the window using the given color without appending linebreaks
* @param text
* @param color
*/
public void showToken(String token, Color color) {
this.appendToTextPane(token, false, color);
}
/**
* Show given RdfQuadruple in the window
* @param quad
*/
public void showQuadruple(RdfQuadruple quad) {
this.showQuadruple(quad, Color.BLACK);
}
/**
* Show given RdfQuadruple in window using given color
* @param quad
* @param color
*/
public void showQuadruple(RdfQuadruple quad, Color color) {
this.appendToTextPane(quad.toString(), true, color);
}
/**
* Internal method to set up the window and show it.
* @param title of the window
*/
private void initWindow(String title) {
// Little hack to be able to access myself from within ActionListeners
final TextObserverWindow textObserverWindow = this;
this.setTitle(title);
this.setResizable(true);
this.setUndecorated(false);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
Container contentPane = this.getContentPane();
contentPane.setLayout(new BorderLayout());
// Add a title label for convenience
JLabel titleLabel = new JLabel(title);
Font labelFont = titleLabel.getFont();
titleLabel.setFont(new Font(labelFont.getName(), Font.PLAIN, 18));
contentPane.add(titleLabel, BorderLayout.NORTH);
// Get a TextPane ready - a little hack to make it always scroll horizontally
this.textPane = new JTextPane() {
private static final long serialVersionUID = 1L;
public boolean getScrollableTracksViewportWidth() {
return false;
}
public void setSize(Dimension dimension) {
if (dimension.width < getParent().getSize().width) {
dimension.width = getParent().getSize().width;
}
super.setSize(dimension);
}
};
this.textPane.setEnabled(true);
this.textPane.setFocusable(false);
this.textPane.setEditable(true);
this.textPane.setAutoscrolls(true);
this.textPane.setMargin(new Insets(5, 5, 5, 5));
contentPane.add(this.textPane, BorderLayout.CENTER);
// Add a ScrollPane
JScrollPane scrollPane = new JScrollPane(this.textPane);
scrollPane.setPreferredSize(new Dimension(TextObserverWindow.TEXTPANE_WIDTH, TextObserverWindow.TEXTPANE_HEIGHT));
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
contentPane.add(scrollPane, BorderLayout.CENTER);
// Bottom Panel contains control elements
JPanel bottomPanel = new JPanel();
// Add a checkbox to toggle autoscroll
JCheckBox autoscrollCheckbox = new JCheckBox();
autoscrollCheckbox.setText("Automatically scroll to bottom");
autoscrollCheckbox.setEnabled(true);
autoscrollCheckbox.setSelected(true);
autoscrollCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBox checkBox = (JCheckBox) e.getSource();
textObserverWindow.setAutoScrollToBottom(checkBox.isSelected());
}
});
bottomPanel.add(autoscrollCheckbox);
// Add buttons to increase/decrease font size
JButton increaseFontSizeButton = new JButton("+");
increaseFontSizeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Font currentFont = textObserverWindow.textPane.getFont();
textObserverWindow.textPane.setFont(
new Font(currentFont.getName(), currentFont.getStyle(), currentFont.getSize()+1)
);
}
});
bottomPanel.add(increaseFontSizeButton);
JButton decreaseFontSizeButton = new JButton("-");
decreaseFontSizeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Font currentFont = textObserverWindow.textPane.getFont();
textObserverWindow.textPane.setFont(
new Font(currentFont.getName(), currentFont.getStyle(), currentFont.getSize()-1)
);
}
});
bottomPanel.add(decreaseFontSizeButton);
// Add bottomPanel to contentPane
contentPane.add(bottomPanel, BorderLayout.SOUTH);
// Finally show the window
this.setVisible(true);
this.pack();
}
/**
* Internal method to append text to the textpane
* @param text to show within the window
* @param addLinebreak whether or not to add a linebreak (usually yes is better)
* @param color to display the text in
*/
private void appendToTextPane(String text, boolean addLinebreak, Color color) {
if (addLinebreak) text += "\n";
StyleContext styleContext = StyleContext.getDefaultStyleContext();
AttributeSet attributeSet = styleContext.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, color);
attributeSet = styleContext.addAttribute(attributeSet, StyleConstants.FontFamily, "Lucida Console");
attributeSet = styleContext.addAttribute(attributeSet, StyleConstants.Alignment, StyleConstants.ALIGN_JUSTIFIED);
int oldCaretPosition = this.textPane.getCaretPosition();
int length = this.textPane.getDocument().getLength();
this.textPane.setCaretPosition(length);
this.textPane.setCharacterAttributes(attributeSet, false);
this.textPane.replaceSelection(text);
// TODO: Fix behaviour when autoscroll is disabled!
if(this.autoScrollToBottom) {
Document textPaneDocument = this.textPane.getDocument();
this.textPane.select(textPaneDocument.getLength(), textPaneDocument.getLength());
} else {
this.textPane.setCaretPosition(oldCaretPosition);
}
}
/**
* Generic method of Observer interface to allow for use as an observer.
* In case no CsparqlQueryResultProxy is being observed, output can still be useful. :-)
* @param observable the incoming observable object
* @param argument the argument that came with it
*/
@Override
public void update(Observable observable, Object argument) {
if(observable instanceof CsparqlQueryResultProxy) {
CsparqlQueryResultProxy result = (CsparqlQueryResultProxy) observable;
RDFTable table = (RDFTable) argument;
this.showCsparqlQueryResult(result, table);
} else {
// Debug output in case this is not used to observe
this.showText(observable.getClass().toString(), Color.RED);
this.showText(observable.toString(), Color.BLUE);
this.showText(argument.getClass().toString(), Color.RED);
this.showText(argument.toString(), Color.BLACK);
}
}
/**
* Method to actually pretty-print the results of a Csparql query
* @param CsparqlQueryResultProxy result
* @param RDFTable table
*/
private void showCsparqlQueryResult(CsparqlQueryResultProxy result, RDFTable table) {
long currentTime = System.currentTimeMillis();
Collection<String> tupleElementNames = table.getNames();
Collection<RDFTuple> tuples = table.getTuples();
Color[] palette = this.getColorPaletteWithSize(table.getNames().size());
this.showText("SystemTime=["+currentTime+"], Results=["+tuples.size()+"]", Color.BLACK);
// Show names in according color coding
this.showToken("Columns in order: [", Color.BLACK);
Iterator<String> elementNamesIterator = tupleElementNames.iterator();
int nameIndex = 0;
while(elementNamesIterator.hasNext()) {
String elementName = elementNamesIterator.next();
if(elementName == "") elementName = "[NO VALUE]";
if(elementNamesIterator.hasNext()) {
elementName += ", ";
}
this.showToken(elementName, palette[nameIndex]);
nameIndex++;
}
this.showText("]", Color.BLACK);
for(RDFTuple tuple : tuples) {
// This one is separated with \t. Due to the implementation of RDFTuple, it is
// impossible to access the list of its fields directly. Sorry for the mess. :-(
String tupleString = tuple.toString();
// Explode tupleString by \t and show each field with alternating color.
StringTokenizer tokenizer = new StringTokenizer(tupleString);
int tokenIndex = 0;
while(tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if(token == "") token = "[NO VALUE]";
if(tokenizer.hasMoreTokens()) {
token += "\t";
} else {
token += "\n";
}
this.showToken(token, palette[tokenIndex]);
tokenIndex++;
}
}
this.showText("End of results ----------------------------------------------------------------- \n", Color.BLACK);
}
/**
* Generates a color palette for the given number of colors
* @param sizeOfPalette number of colors
* @return color palette as an array
*/
private Color[] getColorPaletteWithSize(int sizeOfPalette) {
if(sizeOfPalette == 0) {
return new Color[] {Color.BLACK};
}
Color[] result = new Color[sizeOfPalette];
float h = 0.0f;
float s = 0.75f;
float b = 0.75f;
for(int i=0; i<sizeOfPalette; i++) {
h = (((float) i)/((float) sizeOfPalette));
result[i] = Color.getHSBColor(h, s, b);
}
return result;
}
}