/*******************************************************************************
* Rhythos Editor is a game editor and project management tool for making RPGs on top of the Rhythos Game system.
*
* Copyright (C) 2013 David Maletz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package mrpg.script;
import java.awt.Desktop;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.net.URL;
import javax.swing.AbstractAction;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.BadLocationException;
import mrpg.editor.MapEditor;
import org.fife.rsta.ui.GoToDialog;
import org.fife.rsta.ui.search.FindDialog;
import org.fife.rsta.ui.search.ReplaceDialog;
import org.fife.rsta.ui.search.SearchDialogSearchContext;
import org.fife.ui.autocomplete.AutoCompletion;
import org.fife.ui.autocomplete.CompletionProvider;
import org.fife.ui.autocomplete.DefaultCompletionProvider;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.parser.Parser;
import org.fife.ui.rtextarea.Gutter;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.fife.ui.rtextarea.RUndoManager;
import org.fife.ui.rtextarea.SearchEngine;
public class ScriptTextPane extends RTextScrollPane implements HyperlinkListener, ActionListener {
private static final long serialVersionUID = 9050172784894489328L;
public static final String SYNTAX_STYLE_HAXE = "text/haxe";
private MySyntaxTextArea textArea;
public ScriptTextPane(int w, int h){
super(createTextArea(), true); textArea = (MySyntaxTextArea)getTextArea();
textArea.addHyperlinkListener(this);
CompletionProvider provider = createCompletionProvider();
AutoCompletion ac = new AutoCompletion(provider); ac.install(textArea);
Gutter gutter = getGutter();
gutter.setBookmarkingEnabled(true);
gutter.setBookmarkIcon(MapEditor.getIcon("bookmark"));
setPreferredSize(new Dimension(w,h));
}
public void setParser(Parser p){textArea.clearParsers(); textArea.addParser(p);}
public void reparse(){textArea.forceReparsing(0);}
public void setText(String text) {
textArea.setText(text); textArea.setCaretPosition(0); textArea.discardAllEdits();
}
private static RSyntaxTextArea createTextArea() {
RSyntaxTextArea textArea = new MySyntaxTextArea();
textArea.setTabSize(4);
textArea.setCaretPosition(0);
textArea.requestFocusInWindow();
textArea.setMarkOccurrences(false);
textArea.setCodeFoldingEnabled(true);
textArea.setClearWhitespaceLinesEnabled(false);
textArea.setSyntaxEditingStyle(SYNTAX_STYLE_HAXE);
//textArea.setWhitespaceVisible(true);
//textArea.setPaintMatchedBracketPair(true);
return textArea;
}
private CompletionProvider createCompletionProvider() {
//TODO: create a new CompletionProviderBase class that gets completion results from Haxe.
DefaultCompletionProvider provider = new DefaultCompletionProvider();
return provider;
}
public JMenu createViewMenu(){
JMenu menu = new JMenu("View");
JCheckBoxMenuItem cbItem = new JCheckBoxMenuItem(new CodeFoldingAction());
cbItem.setSelected(true);
menu.add(cbItem);
cbItem = new JCheckBoxMenuItem(new ViewLineHighlightAction());
cbItem.setSelected(true);
menu.add(cbItem);
cbItem = new JCheckBoxMenuItem(new ViewLineNumbersAction());
cbItem.setSelected(true);
menu.add(cbItem);
cbItem = new JCheckBoxMenuItem(new AnimateBracketMatchingAction());
cbItem.setSelected(true);
menu.add(cbItem);
cbItem = new JCheckBoxMenuItem(new BookmarksAction());
cbItem.setSelected(true);
menu.add(cbItem);
cbItem = new JCheckBoxMenuItem(new WordWrapAction());
menu.add(cbItem);
cbItem = new JCheckBoxMenuItem(new ToggleAntiAliasingAction());
cbItem.setSelected(true);
menu.add(cbItem);
cbItem = new JCheckBoxMenuItem(new TabLinesAction());
menu.add(cbItem);
return menu;
}
public JMenu createSearchMenu(){
JMenu menu = new JMenu("Search");
menu.add(new JMenuItem(new ShowFindDialogAction()));
menu.add(new JMenuItem(new ShowReplaceDialogAction()));
menu.add(new JMenuItem(new GoToLineAction()));
return menu;
}
private FindDialog findDialog;
private ReplaceDialog replaceDialog;
private Window owner;
public void initSearchDialogs(Dialog d) {
owner = d; findDialog = new FindDialog(d, this);
replaceDialog = new ReplaceDialog(d, this);
replaceDialog.setSearchContext(findDialog.getSearchContext());
}
public void initSearchDialogs(Frame f) {
owner = f; findDialog = new FindDialog(f, this);
replaceDialog = new ReplaceDialog(f, this);
replaceDialog.setSearchContext(findDialog.getSearchContext());
}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
SearchDialogSearchContext context = findDialog.getSearchContext();
if (FindDialog.ACTION_FIND.equals(command)) {
if (!SearchEngine.find(textArea, context)) {
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
}
} else if (ReplaceDialog.ACTION_REPLACE.equals(command)) {
if (!SearchEngine.replace(textArea, context)) {
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
}
} else if (ReplaceDialog.ACTION_REPLACE_ALL.equals(command)) {
int count = SearchEngine.replaceAll(textArea, context);
JOptionPane.showMessageDialog(null, count
+ " occurrences replaced.");
}
}
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
URL url = e.getURL();
if (url==null) {
UIManager.getLookAndFeel().provideErrorFeedback(null);
}
else {
try{Desktop.getDesktop().browse(url.toURI());}catch(Exception ex){}
}
}
}
private class AnimateBracketMatchingAction extends AbstractAction {
private static final long serialVersionUID = 8261076216280862718L;
public AnimateBracketMatchingAction() {
putValue(NAME, "Animate Bracket Matching");
}
public void actionPerformed(ActionEvent e) {
textArea.setAnimateBracketMatching(
!textArea.getAnimateBracketMatching());
}
}
private class BookmarksAction extends AbstractAction {
private static final long serialVersionUID = -764210212338830406L;
public BookmarksAction() {
putValue(NAME, "Bookmarks");
}
public void actionPerformed(ActionEvent e) {
setIconRowHeaderEnabled(!isIconRowHeaderEnabled());
}
}
private class CodeFoldingAction extends AbstractAction {
private static final long serialVersionUID = 1088597282761061994L;
public CodeFoldingAction() {
putValue(NAME, "Code Folding");
}
public void actionPerformed(ActionEvent e) {
textArea.setCodeFoldingEnabled(!textArea.isCodeFoldingEnabled());
}
}
private class TabLinesAction extends AbstractAction {
private static final long serialVersionUID = -7796352932297988470L;
private boolean selected;
public TabLinesAction() {
putValue(NAME, "Tab Lines");
}
public void actionPerformed(ActionEvent e) {
selected = !selected;
textArea.setPaintTabLines(selected);
}
}
private class ToggleAntiAliasingAction extends AbstractAction {
private static final long serialVersionUID = 6084634739172516759L;
public ToggleAntiAliasingAction() {
putValue(NAME, "Anti-Aliasing");
}
public void actionPerformed(ActionEvent e) {
textArea.setAntiAliasingEnabled(!textArea.getAntiAliasingEnabled());
}
}
private class ViewLineHighlightAction extends AbstractAction {
private static final long serialVersionUID = -6323023498538895804L;
public ViewLineHighlightAction() {
putValue(NAME, "Current Line Highlight");
}
public void actionPerformed(ActionEvent e) {
textArea.setHighlightCurrentLine(
!textArea.getHighlightCurrentLine());
}
}
private class ViewLineNumbersAction extends AbstractAction {
private static final long serialVersionUID = 3393534952794484599L;
public ViewLineNumbersAction() {
putValue(NAME, "Line Numbers");
}
public void actionPerformed(ActionEvent e) {
setLineNumbersEnabled(!getLineNumbersEnabled());
}
}
private class WordWrapAction extends AbstractAction {
private static final long serialVersionUID = -2552189079689556221L;
public WordWrapAction() {
putValue(NAME, "Word Wrap");
}
public void actionPerformed(ActionEvent e) {
textArea.setLineWrap(!textArea.getLineWrap());
}
}
private class GoToLineAction extends AbstractAction {
private static final long serialVersionUID = -4716340028675686542L;
public GoToLineAction() {
super("Go To Line...");
int c = getToolkit().getMenuShortcutKeyMask();
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_G, c));
}
public void actionPerformed(ActionEvent e) {
if (findDialog.isVisible()) {
findDialog.setVisible(false);
}
if (replaceDialog.isVisible()) {
replaceDialog.setVisible(false);
}
GoToDialog dialog; if(owner instanceof Dialog) dialog = new GoToDialog((Dialog)owner);
else dialog = new GoToDialog((Frame)owner);
dialog.setMaxLineNumberAllowed(textArea.getLineCount());
dialog.setVisible(true);
int line = dialog.getLineNumber();
if (line>0) {
try {
textArea.setCaretPosition(textArea.getLineStartOffset(line-1));
} catch (BadLocationException ble) { // Never happens
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
ble.printStackTrace();
}
}
}
}
private class ShowFindDialogAction extends AbstractAction {
private static final long serialVersionUID = -1987487736084782095L;
public ShowFindDialogAction() {
super("Find...");
int c = getToolkit().getMenuShortcutKeyMask();
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_F, c));
}
public void actionPerformed(ActionEvent e) {
if (replaceDialog.isVisible()) {
replaceDialog.setVisible(false);
}
findDialog.setVisible(true);
}
}
private class ShowReplaceDialogAction extends AbstractAction {
private static final long serialVersionUID = -6974058078209198657L;
public ShowReplaceDialogAction() {
super("Replace...");
int c = getToolkit().getMenuShortcutKeyMask();
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_H, c));
}
public void actionPerformed(ActionEvent e) {
if (findDialog.isVisible()) {
findDialog.setVisible(false);
}
replaceDialog.setVisible(true);
}
}
public static interface ModifiedListener {
public void setModified(boolean modified);
}
public String getText(){return textArea.getText();}
public void loadDocument(String doc){textArea.loadDocument(doc, false, null);}
public void loadDocument(String doc, boolean modified){textArea.loadDocument(doc, modified, null);}
public void loadDocument(String doc, boolean modified, ModifiedListener l){textArea.loadDocument(doc, modified, l);}
public void onSave(){textArea.save();}
private static class MySyntaxTextArea extends RSyntaxTextArea {
private static final long serialVersionUID = -3837261840564134591L;
private MyUndoManager manager;
protected RUndoManager createUndoManager(){
int last = 0; ModifiedListener l = null;
if(manager != null){getDocument().removeDocumentListener(manager); last = manager.lastSave; l = manager.listener;}
manager = new MyUndoManager(this); getDocument().addDocumentListener(manager);
manager.lastSave = last; manager.listener = l; return manager;
}
public void loadDocument(String doc, boolean modified, ModifiedListener l){
manager.loadDocument(modified, l); setText(doc); discardAllEdits();
}
public void save(){manager.save(); manager.endInternalAtomicEdit();}
}
private static class MyUndoManager extends RUndoManager implements DocumentListener {
private static final long serialVersionUID = -56221406556737313L;
private int lastSave; private ModifiedListener listener;
public MyUndoManager(RSyntaxTextArea t){super(t);}
public void loadDocument(boolean modified, ModifiedListener l){
lastSave = (modified)?-1:0; listener = l;
}
public void save(){lastSave = edits.indexOf(editToBeUndone())+1;}
public void updateActions(){
super.updateActions(); if(listener != null) listener.setModified(lastSave != edits.indexOf(editToBeUndone())+1);
}
public void changedUpdate(DocumentEvent e){}
public void insertUpdate(DocumentEvent e){if(listener != null) listener.setModified(true);}
public void removeUpdate(DocumentEvent e){if(listener != null) listener.setModified(true);}
}
}