// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.gui;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.EnumMap;
import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
import org.fife.ui.rsyntaxtextarea.folding.CurlyFoldParser;
import org.fife.ui.rsyntaxtextarea.folding.FoldParserManager;
import org.infinity.NearInfinity;
import org.infinity.resource.text.modes.BCSFoldParser;
import org.infinity.resource.text.modes.BCSTokenMaker;
import org.infinity.resource.text.modes.GLSLTokenMaker;
import org.infinity.util.io.FileManager;
import org.infinity.util.io.StreamUtils;
/**
* Extends {@link RSyntaxTextArea} by NearInfinity-specific features.
*/
public class InfinityTextArea extends RSyntaxTextArea
{
/** Available languages for syntax highlighting. */
public enum Language {
/** Disables syntax highlighting */
NONE,
/** Select BCS highlighting. */
BCS,
/** Select GLSL highlighting. */
GLSL,
/** Select LUA highlighting. */
LUA,
/** Select SQL highlighting. */
SQL,
}
/** Available color schemes for use when enabling syntax highlighting. */
public enum Scheme {
/** Disables any color scheme. */
NONE,
/** The default color scheme. */
DEFAULT,
/** Color scheme based on Notepad++'s Obsidian scheme. */
DARK,
/** Color scheme based on Eclipse's defaults. */
ECLIPSE,
/** Color scheme based on IntelliJ IDEA's defaults. */
IDEA,
/** Color scheme based on Microsoft Visual Studio's defaults. */
VS,
/** Color scheme loosely based on the WeiDU Syntax Highlighter for Notepad++. */
BCS,
}
/** The default color of the currently highlighted text line. */
public static final Color DefaultLineHighlightColor = new Color(0xe8e8ff);
/** Color scheme for unicolored text */
public static final String SchemeNone = "org/infinity/resource/text/modes/ThemeNone.xml";
/** Default color scheme */
public static final String SchemeDefault = "org/infinity/resource/text/modes/ThemeDefault.xml";
/** Dark color scheme */
public static final String SchemeDark = "org/infinity/resource/text/modes/ThemeDark.xml";
/** Eclipse color scheme */
public static final String SchemeEclipse = "org/infinity/resource/text/modes/ThemeEclipse.xml";
/** IntelliJ IDEA color scheme */
public static final String SchemeIdea = "org/infinity/resource/text/modes/ThemeIdea.xml";
/** Visual Studio color scheme */
public static final String SchemeVs = "org/infinity/resource/text/modes/ThemeVs.xml";
/** BCS color scheme based on WeiDU Highlighter for Notepad++ */
public static final String SchemeBCS = "org/infinity/resource/text/modes/ThemeBCSLight.xml";
private static EnumMap<Scheme, String> SchemeMap = new EnumMap<Scheme, String>(Scheme.class);
static {
// adding custom code folding definitions
FoldParserManager.get().addFoldParserMapping(BCSTokenMaker.SYNTAX_STYLE_BCS, new BCSFoldParser());
FoldParserManager.get().addFoldParserMapping(GLSLTokenMaker.SYNTAX_STYLE_GLSL, new CurlyFoldParser());
// adding custom syntax highlighting definitions
((AbstractTokenMakerFactory)TokenMakerFactory.getDefaultInstance())
.putMapping(BCSTokenMaker.SYNTAX_STYLE_BCS, "org.infinity.resource.text.modes.BCSTokenMaker");
((AbstractTokenMakerFactory)TokenMakerFactory.getDefaultInstance())
.putMapping(GLSLTokenMaker.SYNTAX_STYLE_GLSL, "org.infinity.resource.text.modes.GLSLTokenMaker");
// initializing color schemes
SchemeMap.put(Scheme.NONE, SchemeNone);
SchemeMap.put(Scheme.DEFAULT, SchemeDefault);
SchemeMap.put(Scheme.DARK, SchemeDark);
SchemeMap.put(Scheme.ECLIPSE, SchemeEclipse);
SchemeMap.put(Scheme.IDEA, SchemeIdea);
SchemeMap.put(Scheme.VS, SchemeVs);
SchemeMap.put(Scheme.BCS, SchemeBCS);
}
/**
* Constructor.
* @param applySettings If {@code true}, applies global text editor settings to this component.
*/
public InfinityTextArea(boolean applySettings)
{
super();
if (applySettings) {
applySettings(true);
applyExtendedSettings(null, null);
}
}
/**
* Constructor.
* @param doc The document for the editor.
* @param applySettings If {@code true}, applies global text editor settings to this component.
*/
public InfinityTextArea(RSyntaxDocument doc, boolean applySettings)
{
super(doc);
if (applySettings) {
applySettings(true);
applyExtendedSettings(null, null);
}
}
/**
* Constructor.
* @param text The initial text to display.
* @param applySettings If {@code true}, applies global text editor settings to this component.
*/
public InfinityTextArea(String text, boolean applySettings)
{
super(text);
if (applySettings) {
applySettings(true);
applyExtendedSettings(null, null);
}
}
/**
* Constructor.
* @param textMode Either {@code INSERT_MODE} or {@code OVERWRITE_MODE}.
* @param applySettings If {@code true}, applies global text editor settings to this component.
*/
public InfinityTextArea(int textMode, boolean applySettings)
{
super(textMode);
if (applySettings) {
applySettings(true);
applyExtendedSettings(null, null);
}
}
/**
* Constructor.
* @param rows The number of rows to display.
* @param cols The number of columns to display.
* @param applySettings If {@code true}, applies global text editor settings to this component.
* @throws IllegalArgumentException If either {@code rows} or {@code cols} is negative.
*/
public InfinityTextArea(int rows, int cols, boolean applySettings)
{
super(rows, cols);
if (applySettings) {
applySettings(true);
applyExtendedSettings(null, null);
}
}
/**
* Constructor.
* @param text The initial text to display.
* @param rows The number of rows to display.
* @param cols The number of columns to display.
* @param applySettings If {@code true}, applies global text editor settings to this component.
* @throws IllegalArgumentException If either {@code rows} or {@code cols} is negative.
*/
public InfinityTextArea(String text, int rows, int cols, boolean applySettings)
{
super(text, rows, cols);
if (applySettings) {
applySettings(true);
applyExtendedSettings(null, null);
}
}
/**
* Constructor.
* @param doc The document for the editor.
* @param text The initial text to display.
* @param rows The number of rows to display.
* @param cols The number of columns to display.
* @param applySettings If {@code true}, applies global text editor settings to this component.
* @throws IllegalArgumentException If either {@code rows} or {@code cols} is negative.
*/
public InfinityTextArea(RSyntaxDocument doc, String text, int rows, int cols, boolean applySettings)
{
super(doc, text, rows, cols);
if (applySettings) {
applySettings(true);
applyExtendedSettings(null, null);
}
}
/**
* Applies global text editor settings to the specified {@link RSyntaxTextArea} component.
* @param resetUndo Specifies whether the undo history will be discarded.
*/
public static void applySettings(RSyntaxTextArea edit, boolean resetUndo)
{
if (edit != null) {
edit.setCurrentLineHighlightColor(DefaultLineHighlightColor);
if (BrowserMenuBar.getInstance() != null) {
edit.setTabsEmulated(BrowserMenuBar.getInstance().isTextTabEmulated());
edit.setTabSize(BrowserMenuBar.getInstance().getTextTabSize());
edit.setWhitespaceVisible(BrowserMenuBar.getInstance().getTextWhitespaceVisible());
edit.setEOLMarkersVisible(BrowserMenuBar.getInstance().getTextEOLVisible());
edit.setHighlightCurrentLine(BrowserMenuBar.getInstance().getTextHighlightCurrentLine());
} else {
// default settings
edit.setTabsEmulated(false);
edit.setTabSize(4);
edit.setWhitespaceVisible(false);
edit.setEOLMarkersVisible(false);
edit.setHighlightCurrentLine(false);
}
if (resetUndo) {
edit.discardAllEdits(); // clearing undo history
}
}
}
/**
* Applies syntax highlighting and color schemes to the specified {@link RSyntaxTextArea} component.
* @param language The language to highlight. Specifying {@code null} uses
* {@code Language.NONE} for syntax hightlighting.
* @param scheme A color scheme to apply to the specified {@link RSyntaxTextArea} component.
* Specifying {@code null} uses the color scheme as defined for the specified
* language.
*/
public static void applyExtendedSettings(RSyntaxTextArea edit, Language language, Scheme scheme)
{
if (edit != null) {
if (language == null) {
language = Language.NONE;
}
// applying syntax highlighting
String style;
switch (language) {
case BCS:
style = BCSTokenMaker.SYNTAX_STYLE_BCS;
break;
case GLSL:
style = GLSLTokenMaker.SYNTAX_STYLE_GLSL;
break;
case LUA:
style = SyntaxConstants.SYNTAX_STYLE_LUA;
break;
case SQL:
style = SyntaxConstants.SYNTAX_STYLE_SQL;
break;
default:
style = SyntaxConstants.SYNTAX_STYLE_NONE;
}
edit.setSyntaxEditingStyle(style);
// applying color scheme
String schemePath;
if (scheme != null) {
// applying explicit color scheme
schemePath = SchemeMap.get(scheme);
if (schemePath == null || schemePath.isEmpty()) {
schemePath = SchemeNone;
}
} else {
// applying implicit color scheme
schemePath = SchemeNone;
switch (language) {
case BCS:
if (BrowserMenuBar.getInstance() != null) {
schemePath = BrowserMenuBar.getInstance().getBcsColorScheme();
}
break;
case GLSL:
if (BrowserMenuBar.getInstance() != null) {
schemePath = BrowserMenuBar.getInstance().getGlslColorScheme();
}
break;
case LUA:
if (BrowserMenuBar.getInstance() != null) {
schemePath = BrowserMenuBar.getInstance().getLuaColorScheme();
}
break;
case SQL:
if (BrowserMenuBar.getInstance() != null) {
schemePath = BrowserMenuBar.getInstance().getSqlColorScheme();
}
break;
default:
}
}
Path schemeFile = FileManager.resolve(schemePath);
boolean isExternal = (NearInfinity.isDebug() && (Files.isRegularFile(schemeFile)));
InputStream is = null;
try {
if (isExternal) {
try {
is = StreamUtils.getInputStream(schemeFile);
} catch (IOException e) {
is = ClassLoader.getSystemResourceAsStream(SchemeNone);
e.printStackTrace();
}
} else {
is = ClassLoader.getSystemResourceAsStream(schemePath);
}
if (is != null) {
try {
Theme theme = Theme.load(is);
if (theme != null) {
theme.apply(edit);
}
} catch (IOException e) {
e.printStackTrace();
}
}
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
is = null;
}
}
// apply code folding
switch (language) {
case BCS:
if (BrowserMenuBar.getInstance() != null) {
edit.setCodeFoldingEnabled(BrowserMenuBar.getInstance().getBcsCodeFoldingEnabled());
} else {
edit.setCodeFoldingEnabled(false);
}
break;
case GLSL:
if (BrowserMenuBar.getInstance() != null) {
edit.setCodeFoldingEnabled(BrowserMenuBar.getInstance().getGlslCodeFoldingEnabled());
} else {
edit.setCodeFoldingEnabled(false);
}
break;
default:
edit.setCodeFoldingEnabled(false);
}
}
}
/**
* Applies global text editor settings to this component.
* @param resetUndo Specifies whether the undo history will be discarded.
*/
public void applySettings(boolean resetUndo)
{
applySettings(this, resetUndo);
}
/**
* Applies syntax highlighting and color schemes to this component.
* @param language The language to highlight. Specifying {@code null} uses
* {@code Language.NONE} for syntax hightlighting.
* @param scheme A color scheme to apply to the specified {@link RSyntaxTextArea} component.
* Specifying {@code null} uses the color scheme as defined for the specified
* language.
*/
public void applyExtendedSettings(Language language, Scheme scheme)
{
applyExtendedSettings(this, language, scheme);
}
@Override
public void setText(String text)
{
// skips carriage return characters
if (text != null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c != '\r') {
sb.append(c);
}
}
super.setText(sb.toString());
} else {
super.setText(null);
}
}
}