334 lines
11 KiB
Java
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;
|
|
|
|
}
|
|
|
|
}
|