package hr.fer.zemris.vhdllab.applets.texteditor.misc;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;
import javax.swing.ActionMap;
import javax.swing.JTextPane;
import javax.swing.LookAndFeel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.TextAction;
/**
* <p>Razred koji predstavlja editor vhdl-a koji zna bojati kljucne rijeci,
* tipove podataka i slicno. Podrzano je i uvlacenje na pritisak tipke
* ENTER.</p>
*
* <p>Prilikom rada s ovom komponentom nije dobro koristiti DocumentListener
* za signalizaciju je li dokument mijenjan ili nije. Naime, bojanje kljucnih
* rijeci se u tom smislu dojavljuje takoder kao promjena. Umjesto toga, komponenta
* omogucava da se preko konstruktora preda referenca na objekt koji implementira
* sucelje {@link ModificationListener} putem kojeg ce se dojavljivati samo promjene
* u tekstu (a ne i u stilu dokumenta). To se sucelje moze koristiti za hvatanje
* signala da je dokument izmijenjen.</p>
*
* <p>Bojanje je izvedeno asinkrono, u smislu da tako dugo dok korisnik nesto tipka
* razumnom brzinom, bojanje ce biti odgodeno. Konkretna analiza teksta zapocet ce tek
* kada korisnik napravi pauzu.</p>
*
* @author marcupic
*
*/
public class KeywordHighlighterTextPane extends JTextPane {
/**
*
*/
private static final long serialVersionUID = 1L;
private boolean dirty;
private long modifTime;
private Timer timer;
protected boolean analysisInProgress;
private Map<String, Style> styleMap;
public KeywordHighlighterTextPane() {
Style plainStyle = this.addStyle("obicni", null);
StyleConstants.setFontFamily(plainStyle, Font.MONOSPACED);
StyleConstants.setFontSize(plainStyle, 12);
Style kwStyle = this.addStyle("kwstil", null);
StyleConstants.setFontFamily(kwStyle, Font.MONOSPACED);
StyleConstants.setFontSize(kwStyle, 12);
StyleConstants.setBold(kwStyle, true);
StyleConstants.setForeground(kwStyle, Color.BLUE);
Style operStyle = this.addStyle("operstil", null);
StyleConstants.setFontFamily(operStyle, Font.MONOSPACED);
StyleConstants.setFontSize(operStyle, 12);
StyleConstants.setBold(operStyle, true);
StyleConstants.setForeground(operStyle, new Color(200,0,0));
Style typeStyle = this.addStyle("typestil", null);
StyleConstants.setFontFamily(typeStyle, Font.MONOSPACED);
StyleConstants.setFontSize(typeStyle, 12);
StyleConstants.setBold(typeStyle, true);
StyleConstants.setForeground(typeStyle, new Color(200,0,200));
Style komentarStyle = this.addStyle("komentarstil", null);
StyleConstants.setFontFamily(komentarStyle, Font.MONOSPACED);
StyleConstants.setFontSize(komentarStyle, 12);
StyleConstants.setForeground(komentarStyle, new Color(0,200,0));
styleMap = new HashMap<String, Style>(32);
styleMap.put("AFTER", kwStyle);
styleMap.put("AND", operStyle);
styleMap.put("ARCHITECTURE", kwStyle);
styleMap.put("ARRAY", kwStyle);
styleMap.put("ASSERT", kwStyle);
styleMap.put("ATTRIBUTE", kwStyle);
styleMap.put("BEGIN", kwStyle);
styleMap.put("BLOCK", kwStyle);
styleMap.put("BIT", typeStyle);
styleMap.put("BUFFER", kwStyle);
styleMap.put("BUS", kwStyle);
styleMap.put("CASE", kwStyle);
styleMap.put("COMPONENT", kwStyle);
styleMap.put("CONFIGURATION", kwStyle);
styleMap.put("CONSTANT", kwStyle);
styleMap.put("DOWNTO", kwStyle);
styleMap.put("ELSE", kwStyle);
styleMap.put("ELSIF", kwStyle);
styleMap.put("END", kwStyle);
styleMap.put("EXIT", kwStyle);
styleMap.put("ENTITY", kwStyle);
styleMap.put("FOR", kwStyle);
styleMap.put("FUNCTION", kwStyle);
styleMap.put("GENERATE", kwStyle);
styleMap.put("GENERIC", kwStyle);
styleMap.put("GUARDED", kwStyle);
styleMap.put("IF", kwStyle);
styleMap.put("IN", kwStyle);
styleMap.put("INERTIAL", kwStyle);
styleMap.put("INOUT", kwStyle);
styleMap.put("IS", kwStyle);
styleMap.put("LIBRARY", kwStyle);
styleMap.put("LOOP", kwStyle);
styleMap.put("MAP", kwStyle);
styleMap.put("NAND", operStyle);
styleMap.put("NEXT", operStyle);
styleMap.put("NOR", operStyle);
styleMap.put("NOT", operStyle);
styleMap.put("NULL", operStyle);
styleMap.put("OF", kwStyle);
styleMap.put("ON", kwStyle);
styleMap.put("OPEN", kwStyle);
styleMap.put("OR", operStyle);
styleMap.put("OTHERS", kwStyle);
styleMap.put("OUT", kwStyle);
styleMap.put("PACKAGE", kwStyle);
styleMap.put("PORT", kwStyle);
styleMap.put("PROCEDURE", kwStyle);
styleMap.put("PROCESS", kwStyle);
styleMap.put("RANGE", kwStyle);
styleMap.put("RECORD", kwStyle);
styleMap.put("REGISTER", kwStyle);
styleMap.put("RETURN", kwStyle);
styleMap.put("SIGNAL", kwStyle);
styleMap.put("SHARED", kwStyle);
styleMap.put("SUBTYPE", kwStyle);
styleMap.put("STD_LOGIC", typeStyle);
styleMap.put("STD_LOGIC_VECTOR", typeStyle);
styleMap.put("THEN", kwStyle);
styleMap.put("TO", kwStyle);
styleMap.put("TRANSPORT", kwStyle);
styleMap.put("TYPE", kwStyle);
styleMap.put("UNTIL", kwStyle);
styleMap.put("USE", kwStyle);
styleMap.put("VARIABLE", kwStyle);
styleMap.put("WAIT", kwStyle);
styleMap.put("WHEN", kwStyle);
styleMap.put("WHILE", kwStyle);
styleMap.put("WITH", kwStyle);
styleMap.put("XNOR", operStyle);
styleMap.put("XOR", operStyle);
setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
timer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
processTImerEvent();
}
});
this.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
if(analysisInProgress) return;
dirty = true;
modifTime = System.currentTimeMillis();
if(!timer.isRunning()) {
timer.start();
}
}
@Override
public void insertUpdate(DocumentEvent e) {
if(analysisInProgress) return;
dirty = true;
modifTime = System.currentTimeMillis();
if(!timer.isRunning()) {
timer.start();
}
}
@Override
public void changedUpdate(DocumentEvent e) {
if(analysisInProgress) return;
dirty = true;
modifTime = System.currentTimeMillis();
if(!timer.isRunning()) {
timer.start();
}
}
});
ActionMap actionMap = this.getActionMap();
actionMap.put(DefaultEditorKit.insertBreakAction, new SmartIndenter());
}
protected void processTImerEvent() {
if(!dirty) {
timer.stop();
return;
}
if(System.currentTimeMillis()-modifTime<450) return;
dirty = false;
timer.stop();
analysisInProgress=true;
analiziraj();
analysisInProgress=false;
}
private void analiziraj() {
StyledDocument doc = this.getStyledDocument();
int docLen = doc.getLength();
String s;
try {
s = doc.getText(0, docLen);
} catch (Exception e1) {
e1.printStackTrace();
return;
}
char[] chars = s.toCharArray();
int poc = 0;
while(poc<chars.length) {
int st = poc;
while(poc<chars.length && chars[poc]!='\n') {
poc++;
}
if(poc<chars.length && chars[poc]=='\n') {
poc++;
}
// Sada imam jedan redak:
int pos = s.indexOf("--", st);
if(pos==-1) {
procesiraj(doc, st, poc, chars, s);
} else if(pos > st) {
procesiraj(doc, st, pos, chars, s);
// komentar je od pos do poc
doc.setCharacterAttributes(pos, poc-pos, doc.getStyle("komentarstil"), true);
} else {
// komentar je od st do poc
doc.setCharacterAttributes(st, poc-st, doc.getStyle("komentarstil"), true);
}
}
}
private void procesiraj(StyledDocument doc, int poc, int kraj, char[] chars, String text) {
while(poc<kraj) {
int cur = poc;
if(Character.isLetter(chars[cur]) || chars[cur]=='_') {
cur++;
while(cur<kraj && (Character.isLetter(chars[cur]) || chars[cur]=='_' || chars[cur]=='.')) {
cur++;
}
String word = text.substring(poc, cur);
Style stil = styleMap.get(word.toUpperCase());
if(stil!=null) {
doc.setCharacterAttributes(poc, cur-poc, stil, true);
} else {
doc.setCharacterAttributes(poc, cur-poc, doc.getStyle("obicni"), true);
}
} else {
cur++;
while(cur<kraj && !Character.isLetter(chars[cur])) {
cur++;
}
doc.setCharacterAttributes(poc, cur-poc, doc.getStyle("obicni"), true);
}
poc = cur;
}
}
private static class SmartIndenter extends TextAction {
private static final long serialVersionUID = 1L;
public SmartIndenter() {
super(DefaultEditorKit.insertBreakAction);
}
@Override
public void actionPerformed(ActionEvent e) {
JTextComponent textPane = getTextComponent(e);
if(textPane==null) return;
if(!textPane.isEditable() || !textPane.isEnabled()) {
LookAndFeel laf = UIManager.getLookAndFeel();
if(laf!=null) laf.provideErrorFeedback(textPane);
return;
}
try {
Document doc = textPane.getDocument();
Element root = doc.getDefaultRootElement();
int selStart = textPane.getSelectionStart();
int line = root.getElementIndex(selStart);
int lineStart = root.getElement(line).getStartOffset();
int lineEnd = root.getElement(line).getEndOffset();
int length = lineEnd - lineStart;
String lineText = doc.getText(lineStart, length);
int emptyChars = countEmptyChars(lineText);
if(selStart - lineStart > emptyChars) {
textPane.replaceSelection("\n"+lineText.substring(0, emptyChars));
} else {
textPane.replaceSelection("\n");
}
} catch(Exception ex) {
LookAndFeel laf = UIManager.getLookAndFeel();
if(laf!=null) laf.provideErrorFeedback(textPane);
}
}
private int countEmptyChars(String text) {
if(text==null || text.isEmpty()) return 0;
int count = 0;
for(; count < text.length(); count++) {
char c = text.charAt(count);
if(c==' ' || c=='\t') continue;
break;
}
return count;
}
}
}