/*
* Copyright 2014 Igor Maznitsa (http://www.igormaznitsa.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.igormaznitsa.prol.easygui;
import java.awt.Color;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleConstants.CharacterConstants;
import com.igormaznitsa.prol.utils.Utils;
/**
* The class implements the Dialog editor for the IDE because it is a very specialized auxiliary class, it is not described very precisely
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class DialogEditor extends AbstractProlEditor implements KeyListener, FocusListener, Runnable, EditorPane.EventReplacer {
private static final long serialVersionUID = 5005224218702033782L;
public class NonClossableReader extends PipedReader {
protected final NonClossableWriter src;
protected NonClossableReader(final NonClossableWriter src) throws IOException {
super(src);
this.src = src;
}
@Override
public void close() {
}
@Override
public synchronized int read() throws IOException {
if (src == null) {
throw new IOException("There is not any connected source.");
}
final Thread thisThread = Thread.currentThread();
while (isWorking) {
final int chr = src.readChar();
if (thisThread.isInterrupted()) {
return -1;
}
if (cancelCurrentRead) {
cancelCurrentRead = false;
return -1;
}
if (chr >= 0) {
return chr;
}
else {
try {
Thread.sleep(10);
}
catch (InterruptedException ex) {
return -1;
}
}
}
return -1;
}
@Override
public synchronized int read(char[] cbuf, int off, int len) throws IOException {
if (src == null) {
throw new IOException("There is not defined any source.");
}
int readLen = 0;
final Thread thisThread = Thread.currentThread();
while (editor.isEditable() && len > 0 && isWorking) {
int chr = src.readChar();
if (thisThread.isInterrupted()) {
return -1;
}
if (cancelCurrentRead) {
cancelCurrentRead = false;
return -1;
}
if (chr < 0) {
try {
Thread.sleep(10);
continue;
}
catch (InterruptedException ex) {
return -1;
}
}
cbuf[off++] = (char) chr;
len--;
readLen++;
}
return readLen;
}
@Override
public synchronized boolean ready() throws IOException {
if (src == null) {
throw new IOException("There is not defined any source.");
}
return !src.isEmpty();
}
}
public final static class NonClossableWriter extends PipedWriter {
protected final List<Character> buffer;
public NonClossableWriter() {
buffer = new ArrayList<Character>();
}
@Override
public void write(final int c) throws IOException {
synchronized (buffer) {
buffer.add((char) c);
}
}
@Override
public void write(final char[] cbuf, final int off, final int len) throws IOException {
int start = off;
final Thread thisThread = Thread.currentThread();
synchronized (buffer) {
for (int li = 0; li < len; li++) {
if (thisThread.isInterrupted()) {
return;
}
buffer.add(cbuf[start++]);
}
}
}
public boolean isEmpty() {
synchronized (buffer) {
return buffer.isEmpty();
}
}
protected int readChar() {
int result = -1;
if (!Thread.currentThread().isInterrupted()) {
synchronized (this.buffer) {
if (!this.buffer.isEmpty()) {
result = buffer.remove(0);
}
}
}
return result;
}
@Override
public void flush() {
}
@Override
public void close() {
}
public void clearBuffer() {
synchronized (this.buffer) {
this.buffer.clear();
}
}
}
private final NonClossableReader inputReader;
private final NonClossableWriter inputWriter;
private final NonClossableWriter outsideWriter;
private final NonClossableReader outsideReader;
private final Thread dialogThread;
private volatile boolean isWorking;
private volatile boolean cancelCurrentRead;
private final SimpleAttributeSet consoleAttribute;
private final SimpleAttributeSet userAttribute;
@Override
public void loadPreferences(Preferences prefs) {
final Color bgColor = extractColor(prefs, "dialogbackcolor", Color.BLUE.darker());
final Color inColor = extractColor(prefs, "dialoginputcolor", Color.GREEN);
final Color outColor = extractColor(prefs, "dialogoutputcolor", Color.ORANGE);
final Color caretColor = extractColor(prefs, "dialogcaretcolor", Color.GREEN);
if (bgColor != null) {
setEdBackground(bgColor);
}
if (inColor != null) {
setEdInputColor(inColor);
}
if (outColor != null) {
setEdOutputColor(outColor);
}
if (caretColor != null) {
setEdCaretColor(caretColor);
}
setEdWordWrap(prefs.getBoolean("dialogwordwrap", true));
setEdFont(loadFontFromPrefs(prefs, "dialogoutputfont"));
}
@Override
public void savePreferences(Preferences prefs) {
prefs.putInt("dialogbackcolor", getEdBackground().getRGB());
prefs.putInt("dialoginputcolor", getEdInputColor().getRGB());
prefs.putInt("dialogoutputcolor", getEdOutputColor().getRGB());
prefs.putInt("dialogcaretcolor", getEdCaretColor().getRGB());
prefs.putBoolean("dialogwordwrap", getEdWordWrap());
saveFontToPrefs(prefs, "dialogoutputfont", editor.getFont());
}
public synchronized void initBeforeSession() {
cancelCurrentRead = false;
inputWriter.clearBuffer();
outsideWriter.clearBuffer();
}
public synchronized void cancelRead() {
cancelCurrentRead = true;
inputWriter.clearBuffer();
outsideWriter.clearBuffer();
}
public Color getEdOutputColor() {
return StyleConstants.getForeground(consoleAttribute);
}
public void setEdOutputColor(Color color) {
StyleConstants.setForeground(consoleAttribute, color);
}
public Color getEdInputColor() {
return StyleConstants.getForeground(userAttribute);
}
public void setEdInputColor(Color color) {
StyleConstants.setForeground(userAttribute, color);
}
@Override
@SuppressWarnings("empty-statement")
public void clearText() {
Utils.assertSwingThread();
super.clearText();
// clear input cache
inputWriter.clearBuffer();
outsideWriter.clearBuffer();
}
public Reader getInputReader() {
return inputReader;
}
public Writer getOutputWriter() {
return outsideWriter;
}
public synchronized void addText(final String text) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
try {
editor.getDocument().insertString(editor.getDocument().getLength(), text, consoleAttribute);
int textLength = editor.getDocument().getLength();
editor.setCharacterAttributes(userAttribute, false);
editor.setCaretPosition(textLength);
}
catch (BadLocationException ex) {
ex.printStackTrace();
}
}
});
}
catch (Throwable thr) {
if (thr instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
throw new Error("Error ", thr);
}
}
public DialogEditor() throws IOException {
super("Dialog");
editor.setEventReplacer(this);
isWorking = true;
removePropertyFromList("EdForeground");
addPropertyToList(new PropertyLink(this, "Output color", "EdOutputColor"));
addPropertyToList(new PropertyLink(this, "Input color", "EdInputColor"));
consoleAttribute = new SimpleAttributeSet();
consoleAttribute.addAttribute(CharacterConstants.Foreground, Color.ORANGE);
consoleAttribute.addAttribute(CharacterConstants.Bold, Boolean.TRUE);
userAttribute = new SimpleAttributeSet();
userAttribute.addAttribute(CharacterConstants.Foreground, Color.CYAN);
userAttribute.addAttribute(CharacterConstants.Bold, Boolean.FALSE);
editor.setContentType("text/plain");
editor.addKeyListener(this);
editor.addFocusListener(this);
outsideWriter = new NonClossableWriter();
outsideReader = new NonClossableReader(outsideWriter);
inputWriter = new NonClossableWriter();
inputReader = new NonClossableReader(inputWriter);
setEnabled(false);
dialogThread = new Thread(this, "prolDialogThread");
dialogThread.setDaemon(true);
dialogThread.start();
editor.setBackground(Color.BLUE.darker().darker().darker().darker());
editor.setForeground(Color.WHITE);
editor.setCaretColor(Color.YELLOW);
editor.setFont(new Font("Arial", Font.BOLD, 14));
editor.setCharacterAttributes(userAttribute, false);
}
@Override
public void keyTyped(KeyEvent e) {
if (e == null) {
return;
}
synchronized (editor.getDocument()) {
int textLength = editor.getDocument().getLength();
editor.setCaretPosition(textLength);
try {
inputWriter.append(e.getKeyChar());
}
catch (Exception ex) {
}
}
}
@Override
public void keyPressed(KeyEvent e) {
editor.setCharacterAttributes(userAttribute, false);
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void focusGained(FocusEvent e) {
editor.setCaretPosition(editor.getDocument().getLength());
}
@Override
public void focusLost(FocusEvent e) {
}
public synchronized void close() {
if (dialogThread != null) {
isWorking = false;
cancelRead();
dialogThread.interrupt();
}
}
@Override
public void run() {
try {
final Thread thisThread = Thread.currentThread();
final StringBuilder builder = new StringBuilder(256);
while (!thisThread.isInterrupted() && isWorking) {
if (outsideReader.ready()) {
synchronized (this) {
while (outsideReader.ready()) {
final int ch = outsideReader.read();
if (ch < 0) {
break;
}
builder.append((char) ch);
}
addText(builder.toString());
builder.setLength(0);
}
}
else {
try {
Thread.sleep(200);
}
catch (InterruptedException ex) {
}
}
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public boolean doesSupportTextPaste() {
return true;
}
@Override
public synchronized void pasteText() {
try {
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
final Transferable contents = clipboard.getContents(null);
if (contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
final String str = (String) contents.getTransferData(DataFlavor.stringFlavor);
if (str != null && !str.isEmpty()) {
inputWriter.write(str.toCharArray(), 0, str.length());
}
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void pasteEvent() {
pasteText();
}
}