/*
* Copyright (C) 2007, 2008 Quadduc <quadduc@gmail.com>
* Copyright (C) 2008 IsmAvatar <IsmAvatar@gmail.com>
*
* This file is part of LateralGM.
* LateralGM is free software and comes with ABSOLUTELY NO WARRANTY.
* See LICENSE for details.
*/
package org.lateralgm.components;
import java.awt.Color;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.GroupLayout.Alignment;
import javax.swing.text.PlainDocument;
import org.lateralgm.components.impl.DocumentUndoManager;
import org.lateralgm.file.ResourceList;
import org.lateralgm.jedit.CompletionMenu;
import org.lateralgm.jedit.DefaultInputHandler;
import org.lateralgm.jedit.GMLKeywords;
import org.lateralgm.jedit.GMLTokenMarker;
import org.lateralgm.jedit.InputHandler;
import org.lateralgm.jedit.JEditTextArea;
import org.lateralgm.jedit.KeywordMap;
import org.lateralgm.jedit.SyntaxDocument;
import org.lateralgm.jedit.Token;
import org.lateralgm.jedit.CompletionMenu.Completion;
import org.lateralgm.main.LGM;
import org.lateralgm.main.Prefs;
import org.lateralgm.main.PrefsStore;
import org.lateralgm.main.UpdateSource.UpdateEvent;
import org.lateralgm.main.UpdateSource.UpdateListener;
import org.lateralgm.messages.Messages;
import org.lateralgm.resources.Resource;
import org.lateralgm.resources.Resource.Kind;
public class GMLTextArea extends JEditTextArea implements UpdateListener
{
private static final long serialVersionUID = 1L;
private static final Kind[] KM_RESOURCES = { Kind.BACKGROUND,Kind.FONT,Kind.OBJECT,Kind.PATH,
Kind.ROOM,Kind.SCRIPT,Kind.SOUND,Kind.SPRITE,Kind.TIMELINE };
private static final GMLKeywords.Keyword[][] GML_KEYWORDS = { GMLKeywords.CONSTRUCTS,
GMLKeywords.FUNCTIONS,GMLKeywords.VARIABLES,GMLKeywords.OPERATORS,GMLKeywords.CONSTANTS };
private final GMLTokenMarker gmlTokenMarker = new GMLTokenMarker();
private final DocumentUndoManager undoManager = new DocumentUndoManager();
protected static Timer timer;
protected Integer lastUpdateTaskID = 0;
private String[][] resourceKeywords = new String[KM_RESOURCES.length][];
protected Completion[] completions;
public GMLTextArea(String text)
{
super();
setDocument(new SyntaxDocument());
getDocument().getDocumentProperties().put(PlainDocument.tabSizeAttribute,Prefs.tabSize);
updateResourceKeywords();
setTokenMarker(gmlTokenMarker);
painter.setFont(Prefs.codeFont);
painter.setStyles(PrefsStore.getSyntaxStyles());
painter.setBracketHighlightColor(Color.gray);
inputHandler = new DefaultInputHandler();
inputHandler.addDefaultKeyBindings();
putClientProperty(InputHandler.KEEP_INDENT_PROPERTY,Boolean.TRUE);
putClientProperty(InputHandler.TAB_TO_INDENT_PROPERTY,Boolean.TRUE);
putClientProperty(InputHandler.CONVERT_TABS_PROPERTY,Boolean.TRUE);
text = text.replace("\r\n","\n"); //$NON-NLS-1$ //$NON-NLS-2$
setText(text);
setCaretPosition(0);
LGM.currentFile.updateSource.addListener(this);
addCaretListener(undoManager);
document.addUndoableEditListener(undoManager);
inputHandler.addKeyBinding("C+Z",undoManager.getUndoAction()); //$NON-NLS-1$
inputHandler.addKeyBinding("C+Y",undoManager.getRedoAction()); //$NON-NLS-1$
inputHandler.addKeyBinding("C+SPACE",new CompletionAction());
}
private static JButton makeToolbarButton(Action a)
{
JButton b = new JButton(a);
b.setToolTipText(b.getText());
b.setText(null);
b.setRequestFocusEnabled(false);
return b;
}
private JButton makeInputHandlerToolbarButton(final ActionListener l, String key)
{
final GMLTextArea source = this;
Action a = new AbstractAction(Messages.getString(key),LGM.getIconForKey(key))
{
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e)
{
inputHandler.executeAction(l,source,null);
}
};
return makeToolbarButton(a);
}
public void addEditorButtons(JToolBar tb)
{
tb.add(makeToolbarButton(getUndoManager().getUndoAction()));
tb.add(makeToolbarButton(getUndoManager().getRedoAction()));
tb.add(makeToolbarButton(getGotoLineAction()));
tb.addSeparator();
tb.add(makeInputHandlerToolbarButton(InputHandler.CUT,"GMLTextArea.CUT")); //$NON-NLS-1$
tb.add(makeInputHandlerToolbarButton(InputHandler.COPY,"GMLTextArea.COPY")); //$NON-NLS-1$
tb.add(makeInputHandlerToolbarButton(InputHandler.PASTE,"GMLTextArea.PASTE")); //$NON-NLS-1$
}
private Action getGotoLineAction()
{
return new AbstractAction(Messages.getString("GMLTextArea.GOTO_LINE"), //$NON-NLS-1$
LGM.getIconForKey("GMLTextArea.GOTO_LINE")) //$NON-NLS-1$
{
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent arg0)
{
final JDialog d = new JDialog((Frame) null,true);
JPanel p = new JPanel();
GroupLayout layout = new GroupLayout(p);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
p.setLayout(layout);
JLabel l = new JLabel("Line: ");
NumberField f = new NumberField(getCaretLine());
f.selectAll();
JButton b = new JButton("Goto");
b.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
d.setVisible(false);
}
});
layout.setHorizontalGroup(layout.createParallelGroup()
/**/.addGroup(layout.createSequentialGroup()
/* */.addComponent(l)
/* */.addComponent(f))
/**/.addComponent(b,Alignment.CENTER));
layout.setVerticalGroup(layout.createSequentialGroup()
/**/.addGroup(layout.createParallelGroup()
/* */.addComponent(l)
/* */.addComponent(f))
/**/.addComponent(b));
// JOptionPane.showMessageDialog(null,p);
d.setContentPane(p);
d.pack();
d.setResizable(false);
d.setLocationRelativeTo(null);
d.setVisible(true); //blocks until user clicks OK
int line = f.getIntValue();
int lines = getLineCount();
if (line < 0) line = lines + line;
if (line < 0) line = 0;
if (line >= lines) line = lines - 1;
setCaretPosition(getLineStartOffset(line));
}
};
}
public DocumentUndoManager getUndoManager()
{
return undoManager;
}
public String getTextCompat()
{
String s = getText();
s = s.replaceAll("\r?\n","\r\n"); //$NON-NLS-1$ //$NON-NLS-2$
return s;
}
public void updateResourceKeywords()
{
for (int j = 0; j < resourceKeywords.length; j++)
{
ResourceList<?> rl = LGM.currentFile.getList(KM_RESOURCES[j]);
int l = rl.size();
String[] a = new String[l];
int i = 0;
for (Resource<?,?> r : rl)
a[i++] = r.getName();
resourceKeywords[j] = a;
}
completions = null;
updateTokenMarker();
}
private void updateTokenMarker()
{
KeywordMap km = new KeywordMap(false);
for (String[] a : resourceKeywords)
{
for (String s : a)
{
if (s.length() > 0) km.add(s,Token.KEYWORD3);
}
}
gmlTokenMarker.setCustomKeywords(km);
}
protected void updateCompletions()
{
int l = 0;
for (String[] a : resourceKeywords)
{
l += a.length;
}
for (GMLKeywords.Keyword[] a : GML_KEYWORDS)
{
l += a.length;
}
completions = new Completion[l];
int i = 0;
for (String[] a : resourceKeywords)
{
for (String s : a)
{
completions[i] = new CompletionMenu.WordCompletion(s);
i++;
}
}
for (GMLKeywords.Keyword[] a : GML_KEYWORDS)
{
for (GMLKeywords.Keyword k : a)
{
if (k instanceof GMLKeywords.Function)
completions[i] = new FunctionCompletion((GMLKeywords.Function) k);
else if (k instanceof GMLKeywords.Variable)
completions[i] = new VariableCompletion((GMLKeywords.Variable) k);
else
completions[i] = new CompletionMenu.WordCompletion(k.getName());
i++;
}
}
}
public class VariableCompletion extends CompletionMenu.Completion
{
private final GMLKeywords.Variable variable;
public VariableCompletion(GMLKeywords.Variable v)
{
variable = v;
name = v.getName();
}
public boolean apply(JEditTextArea a, char input, int offset, int pos, int length)
{
String s = name;
int l = input != '\0' ? pos : length;
int p = s.length();
if (variable.arraySize > 0)
{
s += "[]";
boolean ci = true;
switch (input)
{
case '\0':
case '[':
break;
case ']':
ci = false;
break;
default:
s += String.valueOf(input);
}
if (ci)
p = s.length() - 1;
else
p = s.length();
}
SyntaxDocument d = a.getDocument();
if (!replace(d,offset,l,s)) return false;
a.setCaretPosition(offset + p);
return true;
}
public String toString()
{
String s = name;
if (variable.arraySize > 0) s += "[0.." + String.valueOf(variable.arraySize - 1) + "]";
if (variable.readOnly) s += "*";
return s;
}
}
public class FunctionCompletion extends CompletionMenu.Completion
{
private final GMLKeywords.Function function;
public FunctionCompletion(GMLKeywords.Function f)
{
function = f;
name = f.getName();
}
public boolean apply(JEditTextArea a, char input, int offset, int pos, int length)
{
String s = name + "(" + getArguments() + ")";
int l = input != '\0' ? pos : length;
int p1, p2;
boolean argSel = true;
switch (input)
{
case '\0':
case '(':
break;
case ')':
argSel = false;
break;
default:
s += String.valueOf(input);
}
if (argSel && function.arguments.length > 0)
{
p1 = name.length() + 1;
p2 = p1 + getArgument(0).length();
}
else
{
p1 = s.length();
p2 = p1;
}
SyntaxDocument d = a.getDocument();
if (!replace(d,offset,l,s)) return false;
a.setSelectionStart(offset + p1);
a.setSelectionEnd(offset + p2);
return true;
}
public String getArgument(int i)
{
if (i >= function.arguments.length) return null;
return function.arguments[i] + (i == function.dynArgIndex ? "..." : "");
}
public String getArguments()
{
String s = "";
for (int i = 0; i < function.arguments.length; i++)
s += (i > 0 ? "," : "") + getArgument(i);
return s;
}
public String toString()
{
return name + "(" + getArguments() + ")";
}
}
private class CompletionAction implements ActionListener
{
public CompletionAction()
{
super();
}
private String find(String input, String regex)
{
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
if (m.find()) return m.group();
return "";
}
public void actionPerformed(ActionEvent e)
{
if (editable)
{
int s = getSelectionStart();
int sl = getSelectionStartLine();
int ls = s - getLineStartOffset(sl);
String lt = getLineText(sl);
int l1 = find(lt.substring(0,ls),"\\w+$").length();
int l2 = find(lt.substring(ls),"^\\w+").length();
if (completions == null) updateCompletions();
new CompletionMenu(LGM.frame,GMLTextArea.this,s - l1,l1,l1 + l2,completions);
}
}
}
public void updated(UpdateEvent e)
{
if (timer == null) timer = new Timer();
timer.schedule(new UpdateTask(),500);
}
private class UpdateTask extends TimerTask
{
private int id;
public UpdateTask()
{
synchronized (lastUpdateTaskID)
{
id = ++lastUpdateTaskID;
}
}
public void run()
{
synchronized (lastUpdateTaskID)
{
if (id != lastUpdateTaskID) return;
}
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
updateResourceKeywords();
int fl = getFirstLine();
painter.invalidateLineRange(fl,fl + getVisibleLines());
}
});
}
}
}