/* * Copyright (c) 2010-2020, sikuli.org, sikulix.com - MIT license */ package org.sikuli.ide; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.PrintStream; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.*; import javax.swing.text.*; import javax.swing.text.html.HTML; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import org.sikuli.basics.Debug; // // A simple Java Console for your application (Swing version) // Requires Java 1.1.5 or higher // // Disclaimer the use of this source is at your own risk. // // Permision to use and distribute into your own applications // // RJHM van den Bergh , [email protected] import org.sikuli.basics.PreferencesUser; import org.sikuli.script.SX; import org.sikuli.script.support.IScriptRunner; import org.sikuli.script.support.Runner; public class EditorConsolePane extends JPanel implements Runnable { private static final String me = "EditorConsolePane: "; static boolean ENABLE_IO_REDIRECT = true; static { String flag = System.getProperty("sikuli.console"); if (flag != null && flag.equals("false")) { ENABLE_IO_REDIRECT = false; } } private int NUM_PIPES; private JTextPane textArea; private Thread[] reader; private boolean quit; private PipedInputStream[] pin; private JPopupMenu popup; Thread errorThrower; // just for testing (Throws an Exception at this Console) class PopupListener extends MouseAdapter { JPopupMenu popup; PopupListener(JPopupMenu popupMenu) { popup = popupMenu; } public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { popup.show(e.getComponent(), e.getX(), e.getY()); } } } public EditorConsolePane() { super(); textArea = new JTextPane(); textArea.setEditorKit(new HTMLEditorKit()); textArea.setTransferHandler(new JTextPaneHTMLTransferHandler()); String css = PreferencesUser.get().getConsoleCSS(); ((HTMLEditorKit) textArea.getEditorKit()).getStyleSheet().addRule(css); textArea.setEditable(false); setLayout(new BorderLayout()); add(new JScrollPane(textArea), BorderLayout.CENTER); if (ENABLE_IO_REDIRECT) { Debug.log(3, "EditorConsolePane: starting redirection to message area"); int npipes = 2; NUM_PIPES = npipes * Runner.getRunners().size() + npipes; pin = new PipedInputStream[NUM_PIPES]; reader = new Thread[NUM_PIPES]; for (int i = 0; i < NUM_PIPES; i++) { pin[i] = new PipedInputStream(); } // redirect System IO to console try { PipedOutputStream oout = new PipedOutputStream(pin[0]); PrintStream ops = new PrintStream(oout, true); System.setOut(ops); reader[0] = new Thread(EditorConsolePane.this); reader[0].setDaemon(true); reader[0].start(); PipedOutputStream eout = new PipedOutputStream(pin[1]); PrintStream eps = new PrintStream(eout, true); System.setErr(eps); reader[1] = new Thread(EditorConsolePane.this); reader[1].setDaemon(true); reader[1].start(); int irunner = 1; for (IScriptRunner srunner : Runner.getRunners()) { Debug.log(3, "EditorConsolePane: redirection for %s", srunner.getName()); PipedOutputStream pout = new PipedOutputStream(pin[irunner * npipes]); PrintStream psout = new PrintStream(pout, true); PipedOutputStream perr = new PipedOutputStream(pin[irunner * npipes + 1]); PrintStream pserr = new PrintStream(perr, true); srunner.redirect(psout, pserr); quit = false; // signals the Threads that they should exit // Starting two seperate threads to read from the PipedInputStreams for (int i = irunner * npipes; i < irunner * npipes + npipes; i++) { reader[i] = new Thread(EditorConsolePane.this); reader[i].setDaemon(true); reader[i].start(); } irunner++; } } catch (IOException e1) { Debug.log(-1, "Redirecting System IO failes", e1.getMessage()); } } //Create the popup menu. popup = new JPopupMenu(); JMenuItem menuItem = new JMenuItem("Clear messages"); // Add ActionListener that clears the textArea menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textArea.setText(""); } }); popup.add(menuItem); //Add listener to components that can bring up popup menus. MouseListener popupListener = new PopupListener(popup); textArea.addMouseListener(popupListener); } private void appendMsg(String msg) { HTMLDocument doc = (HTMLDocument) textArea.getDocument(); HTMLEditorKit kit = (HTMLEditorKit) textArea.getEditorKit(); try { kit.insertHTML(doc, doc.getLength(), msg, 0, 0, null); } catch (Exception e) { Debug.error(me + "Problem appending text to message area!\n%s", e.getMessage()); } } /* public synchronized void windowClosed(WindowEvent evt) { quit=true; this.notifyAll(); // stop all threads try { reader.join(1000);pin.close(); } catch (Exception e){} try { reader2.join(1000);pin2.close(); } catch (Exception e){} System.exit(0); } public synchronized void windowClosing(WindowEvent evt) { frame.setVisible(false); // default behaviour of JFrame frame.dispose(); } */ static final String lineSep = System.getProperty("line.separator"); private String htmlize(String msg) { StringBuilder sb = new StringBuilder(); Pattern patMsgCat = Pattern.compile("\\[(.+?)\\].*"); msg = msg.replace("&", "&").replace("<", "<").replace(">", ">"); String cls = "normal"; for (String line : msg.split(lineSep)) { Matcher m = patMsgCat.matcher(line); if (m.matches()) { cls = m.group(1); } line = "<pre class=\"" + cls + "\" style=\"margin: 0;\">" + line + "</pre>"; sb.append(line); } return sb.toString(); } @Override public synchronized void run() { for (int i = 0; i < NUM_PIPES; i++) { while (Thread.currentThread() == reader[i]) { try { this.wait(100); } catch (InterruptedException ie) { } int availableToRead = 0; try { availableToRead = pin[i].available(); } catch (IOException e) { } if (availableToRead != 0) { String input = null; try { input = this.readLine(pin[i]); } catch (IOException e) { } if (null != input) { String finalInput = input; EventQueue.invokeLater(() -> { synchronized (textArea) { appendMsg(htmlize(finalInput)); //appendMsg("</br>"); //trial to avoid problems with Utilities.getRowStart int textLen = textArea.getDocument().getLength(); if (textLen > 0) { int textPosEnd = textLen - 1; int rowStart = -1; try { //TODO horizontal slider should be left adjusted with messages exceeding window width rowStart = textPosEnd; // currently right adjusted in these cases // if (false) { // currently noop // rowStart = Math.max(0, Utilities.getRowStart(textArea, textPosEnd)); // } } catch (Exception e) { rowStart = textPosEnd; // fallback when using Utilities.getRowStart } textArea.setCaretPosition(rowStart); } } }); } } if (quit) { return; } } } } public synchronized String readLine(PipedInputStream in) throws IOException { String input = ""; do { int available = in.available(); if (available == 0) { break; } byte b[] = new byte[available]; in.read(b); input = input + new String(b, 0, b.length); } while (!input.endsWith("\n") && !input.endsWith("\r\n") && !quit); return input; } public void clear() { textArea.setText(""); } } class JTextPaneHTMLTransferHandler extends TransferHandler { private static final String me = "EditorConsolePane: "; public JTextPaneHTMLTransferHandler() { } @Override public void exportToClipboard(JComponent comp, Clipboard clip, int action) { super.exportToClipboard(comp, clip, action); } @Override public int getSourceActions(JComponent c) { return COPY_OR_MOVE; } @Override protected Transferable createTransferable(JComponent c) { JTextPane aTextPane = (JTextPane) c; HTMLEditorKit kit = ((HTMLEditorKit) aTextPane.getEditorKit()); StyledDocument sdoc = aTextPane.getStyledDocument(); int sel_start = aTextPane.getSelectionStart(); int sel_end = aTextPane.getSelectionEnd(); int i = sel_start; StringBuilder output = new StringBuilder(); while (i < sel_end) { Element e = sdoc.getCharacterElement(i); Object nameAttr = e.getAttributes().getAttribute(StyleConstants.NameAttribute); int start = e.getStartOffset(), end = e.getEndOffset(); if (nameAttr == HTML.Tag.BR) { output.append("\n"); } else if (nameAttr == HTML.Tag.CONTENT) { if (start < sel_start) { start = sel_start; } if (end > sel_end) { end = sel_end; } try { String str = sdoc.getText(start, end - start); output.append(str); } catch (BadLocationException ble) { Debug.error(me + "Copy-paste problem!\n%s", ble.getMessage()); } } i = end; } return new StringSelection(output.toString()); } }