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 tupleElementNames = table.getNames(); Collection 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 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