/*******************************************************************************
gocha.org-lib-java Библеотека общего назначения
(с) Камнев Георгий Павлович 2009 GPLv2
Данная программа является свободным программным обеспечением. Вы вправе
распространять ее и/или модифицировать в соответствии с условиями версии 2
либо по вашему выбору с условиями более поздней версии
Стандартной Общественной Лицензии GNU, опубликованной Free Software Foundation.
Мы распространяем данную программу в надежде на то, что она будет вам полезной,
однако НЕ ПРЕДОСТАВЛЯЕМ НА НЕЕ НИКАКИХ ГАРАНТИЙ,
в том числе ГАРАНТИИ ТОВАРНОГО СОСТОЯНИЯ ПРИ ПРОДАЖЕ
и ПРИГОДНОСТИ ДЛЯ ИСПОЛЬЗОВАНИЯ В КОНКРЕТНЫХ ЦЕЛЯХ.
Для получения более подробной информации ознакомьтесь
со Стандартной Общественной Лицензией GNU.
Вместе с данной программой вы должны были получить экземпляр
Стандартной Общественной Лицензии GNU.
Если вы его не получили, сообщите об этом в Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*******************************************************************************/
package org.gocha.gui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyledDocument;
import org.gocha.gui.highlight.TextStyleObject;
import org.gocha.gui.highlight.TextStyles;
import org.gocha.text.regex.Matcher;
import org.gocha.text.regex.Pattern;
/**
* @author gocha
*/
public class Highlighter
{
private JTextPane textPane = null;
private String oldText = null;
private Timer timer = null;
private boolean useBackgroundThread = true;
private Thread thread = null;
public Highlighter()
{
initTimer();
}
public void reinit()
{
reinitStyles();
highlight();
}
private void reinitStyles()
{
if( this.styles!=null && getTextPane()!=null )
{
this.styles.applyTo(getTextPane());
}
}
private TextStyles styles = null;
public TextStyles getStyles()
{
return styles;
}
public void setStyles(TextStyles styles)
{
this.styles = styles;
reinitStyles();
}
public JTextPane getTextPane()
{
return textPane;
}
public void setTextPane(JTextPane textPane)
{
this.textPane = textPane;
needHighlight = true;
}
private Pattern pattern = null;
public Pattern getPattern()
{
return pattern;
}
public void setPattern(Pattern pattern)
{
this.pattern = pattern;
}
private boolean needHighlight = false;
public void stop()
{
start(false);
}
public void start()
{
start(true);
}
public void start(boolean start)
{
if( isStarted()!=start )
{
if( start )
{
if( timer!=null )
{
timer.start();
}
if( useBackgroundThread )
{
if( thread==null )
{
thread = new Thread(backgroundHighlightWorker);
thread.setDaemon(true);
thread.setPriority(Thread.MIN_PRIORITY);
}
if( !thread.isAlive() )thread.start();
}
}else{
if( timer!=null )
{
timer.stop();
}
if( useBackgroundThread )
{
if( thread!=null && thread.isAlive() )
{
thread.interrupt();
while(true)
{
try {
Thread.sleep(10);
}
catch (InterruptedException ex) {
break;
}
if( !thread.isAlive() )break;
}
}
if( thread!=null && thread.isAlive() )
{
thread.stop();
}
thread = null;
}
}
}
}
public boolean isStarted()
{
if( timer==null )return false;
return timer.isRunning();
}
private Style clearStyle = null;
public void clearHighlight()
{
JTextPane tpane = getTextPane();
if( tpane==null )return;
if( clearStyle==null )
clearStyle = tpane.getStyledDocument().addStyle(null, null);
StyledDocument sdoc = tpane.getStyledDocument();
int len = sdoc.getLength();
if( len>0 )
{
sdoc.setCharacterAttributes(0, len, clearStyle, true);
}
}
public int getDelay()
{
if( timer==null )return 1000;
return timer.getDelay();
}
public void setDelay(int delay)
{
boolean started = isStarted();
stop();
if( timer!=null )timer.setDelay(delay);
start(started);
}
private void initTimer() {
timer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
onTimer();
}
});
// timer.start();
}
private void onTimer()
{
checkChanged();
if( needHighlight )
{
highlight();
needHighlight = false;
}
}
/**
* Возвращает текущий редактируемый текст
* @return Текущий редактируемый текст
*/
private String existsText()
{
if( textPane==null )return "";
int len = textPane.getStyledDocument().getLength();
String txt = null;
try {
txt = textPane.getStyledDocument().getText(0, len);
} catch (BadLocationException ex) {
ex.printStackTrace();
}
return txt;
}
/**
* Проверяет необходимо ли произвести подсветку
*/
private void checkChanged()
{
if( oldText==null )
{
oldText = existsText();
needHighlight = true;
return;
}
String existsTxt = existsText();
if( !oldText.equals(existsTxt) )
{
oldText = existsTxt;
needHighlight = true;
}
}
private void highlight()
{
if( textPane==null )return;
if( pattern==null )return;
if( styles==null )return;
if( useBackgroundThread )
{
addHighlightRequest(textPane, pattern, styles);
}else{
awtThreadHighlight();
}
}
/**
* Выполняется в потоке AWT
*/
private void awtThreadHighlight()
{
StyledDocument sdoc = textPane.getStyledDocument();
int sdocLen = sdoc.getLength();
String sdocText = null;
try {
sdocText = sdoc.getText(0, sdocLen);
}
catch (BadLocationException ex) {
ex.printStackTrace();
}
if( sdocText==null )return;
clearApplyStyleQueue();
Matcher matched = pattern.match(sdocText, 0);
if( matched.isMatched() )
{
for( Matcher m : matched.walk() )
{
addMatched(m);
}
}
execApplyStyleQueue();
}
/**
* Задания фоновому потоку на подстветку
*/
private final Queue<Runnable> queueHighlightRequest = new ArrayDeque<Runnable>();
/**
* Добавляем задание на перерисовку/подстветку синтаксиса
* @param textPane Подсвечиваемый текст
* @param ptrn Синтаксис
* @param styles Стиль подстветки
*/
private void addHighlightRequest(JTextPane textPane, Pattern ptrn, TextStyles styles)
{
if( textPane==null )return;
if( ptrn==null )return;
if( styles==null )return;
StyledDocument sdoc = textPane.getStyledDocument();
int sdocLen = sdoc.getLength();
String __sourceText = null;
try {
__sourceText = sdoc.getText(0, sdocLen);
}
catch (BadLocationException ex) {
return;
}
// Исходный текст
final String fSourceText = __sourceText;
final Pattern fPattern = ptrn;
Runnable bgRun = new Runnable() {
@Override
public void run()
{
final Matcher matched = fPattern.match(fSourceText, 0);
if( !matched.isMatched() )return;
Runnable awtRun = new Runnable() {
@Override
public void run()
{
String nowText = existsText();
if( !fSourceText.equals(nowText) )return;
clearApplyStyleQueue();
for( Matcher m : matched.walk() )
{
addMatched(m);
}
execApplyStyleQueue();
}
};
SwingUtilities.invokeLater(awtRun);
}
};
synchronized( queueHighlightRequest )
{
queueHighlightRequest.add(bgRun);
}
}
/**
* Фоновый поток (тело цикла).
* Читает задания на подсветку синтаксиса и выполняет последнее задание.
*/
private Runnable backgroundHighlightWorker = new Runnable() {
@Override
public void run()
{
// System.out.println("Background highlight started");
while(true)
{
if( Thread.interrupted() )break;
Runnable job = null;
// Берем последнее (актуальное) задание
synchronized(queueHighlightRequest)
{
while(!queueHighlightRequest.isEmpty())
{
job = queueHighlightRequest.poll();
}
}
// Запускаем задание на исполнение
if( job!=null )job.run();
try {
Thread.sleep(10);
}
catch (InterruptedException ex) {
// Logger.getLogger(Highlighter.class.getName()).log(Level.SEVERE, null, ex);
break;
}
}
// System.out.println("Background highlight stopped");
}
};
private Queue<Runnable> queueApplyStyle = new ArrayDeque<Runnable>();
private void clearApplyStyleQueue()
{
queueApplyStyle.clear();
}
private void execApplyStyleQueue()
{
while(true)
{
Runnable r = queueApplyStyle.poll();
if( r==null )break;
r.run();
}
}
private void addMatched(Matcher m)
{
if( m==null )return;
Runnable r = createApplyStyle(m);
if( r==null )return;
queueApplyStyle.add(r);
}
private Runnable createApplyStyle(Matcher m)
{
final Matcher fM = m;
return new Runnable() {
@Override
public void run()
{
String name = fM.getName();
if( name!=null )applyStyle(name, fM);
}
};
}
private void applyStyle(String id,Matcher m)
{
if( id==null )return;
if( m==null )return;
if( !m.isMatched() )return;
if( getStyles()==null )return;
JTextPane tp = getTextPane();
if( tp==null )return;
StyledDocument doc = tp.getStyledDocument();
TextStyleObject so = getStyles().get(id);
Style s = so==null ? null : so.getStyle();
if( s==null )return;
int begin = m.getBegin();
int len = m.getLength();
doc.setCharacterAttributes(begin, len, s, true);
}
}