diff --git a/designer-base/src/main/java/com/fr/design/gui/ispinner/UnsignedIntUISpinner.java b/designer-base/src/main/java/com/fr/design/gui/ispinner/UnsignedIntUISpinner.java new file mode 100644 index 000000000..0a04eabf5 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/ispinner/UnsignedIntUISpinner.java @@ -0,0 +1,31 @@ +package com.fr.design.gui.ispinner; + +import com.fr.design.gui.itextfield.UIIntNumberField; +import com.fr.design.gui.itextfield.UINumberField; + +/** + * Created by IntelliJ IDEA. + * Author : Hugh.C + * Date: 19-08-28 + * Time: 下午2:19 + */ +public class UnsignedIntUISpinner extends UISpinner { + + + public UnsignedIntUISpinner(double minValue, double maxValue, double dierta) { + super(minValue, maxValue, dierta); + } + + public UnsignedIntUISpinner(double minValue, double maxValue, double dierta, double defaultValue) { + super(minValue, maxValue, dierta, defaultValue); + } + + @Override + protected UINumberField initNumberField() { + return new UIIntNumberField() { + public boolean shouldResponseChangeListener() { + return false; + } + }; + } +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/gui/itable/AbstractPropertyTable.java b/designer-base/src/main/java/com/fr/design/gui/itable/AbstractPropertyTable.java index 4367d8f6b..ccdd8eb64 100644 --- a/designer-base/src/main/java/com/fr/design/gui/itable/AbstractPropertyTable.java +++ b/designer-base/src/main/java/com/fr/design/gui/itable/AbstractPropertyTable.java @@ -3,11 +3,15 @@ */ package com.fr.design.gui.itable; -import java.awt.*; +import com.fr.general.ComparatorUtils; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; - import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.table.AbstractTableModel; @@ -16,9 +20,6 @@ import javax.swing.table.JTableHeader; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; -import com.fr.general.ComparatorUtils; - - /** * @author richer * @since 6.5.3 @@ -109,8 +110,8 @@ public abstract class AbstractPropertyTable extends JTable { @Override public TableCellEditor getCellEditor(int row, int column) { - if (groups != null) { - Point pIndex = getGroupIndex(row); + Point pIndex = getGroupIndex(row); + if (groups != null && pIndex != null) { PropertyGroup group = groups.get(pIndex.x); if (pIndex.y == 0) { return super.getCellEditor(row, column); @@ -162,7 +163,7 @@ public abstract class AbstractPropertyTable extends JTable { int row = AbstractPropertyTable.super.rowAtPoint(e.getPoint()); if (row != -1) { Point pIndex = getGroupIndex(row); - if (pIndex.y == 0 && e.getClickCount() > 1) { + if (pIndex != null && pIndex.y == 0 && e.getClickCount() > 1) { toggleCollapse(pIndex.x); } } @@ -180,7 +181,7 @@ public abstract class AbstractPropertyTable extends JTable { int row = AbstractPropertyTable.super.rowAtPoint(e.getPoint()); if (row != -1) { Point pIndex = getGroupIndex(row); - if (pIndex.y == 0 && e.getClickCount() == 1 && e.getX() < PROPERTY_ICON_WIDTH) { + if (pIndex != null && pIndex.y == 0 && e.getClickCount() == 1 && e.getX() < PROPERTY_ICON_WIDTH) { toggleCollapse(pIndex.x); } } @@ -263,6 +264,9 @@ public abstract class AbstractPropertyTable extends JTable { @Override public void setValueAt(Object aValue, int row, int column) { Point pIndex = getGroupIndex(row); + if (pIndex == null) { + return; + } PropertyGroup group = groups.get(pIndex.x); if (pIndex.y != 0) { Object old_value = getValueAt(row, column); @@ -276,16 +280,11 @@ public abstract class AbstractPropertyTable extends JTable { @Override public boolean isCellEditable(int row, int column) { Point pIndex = getGroupIndex(row); - PropertyGroup group = groups.get(pIndex.x); - if (pIndex.y == 0) { - if (column == 0) { - return false; - } else { - return false; - } - } else { - return column == 1 && group.getModel().isEditable(pIndex.y - 1); + if (pIndex == null) { + return false; } + PropertyGroup group = groups.get(pIndex.x); + return pIndex.y != 0 && (column == 1 && group.getModel().isEditable(pIndex.y - 1)); } } -} \ No newline at end of file +} diff --git a/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java b/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java index c431e329c..6aa0cd8e4 100644 --- a/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java +++ b/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java @@ -2,29 +2,39 @@ * 08/29/2004 * * RSyntaxTextAreaEditorKit.java - The editor kit used by RSyntaxTextArea. - * + * * This library is distributed under a modified BSD license. See the included * RSyntaxTextArea.License.txt file for details. */ package com.fr.design.gui.syntax.ui.rsyntaxtextarea; -import java.awt.*; -import java.awt.event.*; -import java.util.ResourceBundle; -import java.util.Stack; -import javax.swing.*; -import javax.swing.text.*; - import com.fr.design.gui.syntax.ui.rsyntaxtextarea.folding.Fold; import com.fr.design.gui.syntax.ui.rsyntaxtextarea.folding.FoldCollapser; import com.fr.design.gui.syntax.ui.rsyntaxtextarea.folding.FoldManager; import com.fr.design.gui.syntax.ui.rsyntaxtextarea.templates.CodeTemplate; import com.fr.design.gui.syntax.ui.rtextarea.Gutter; import com.fr.design.gui.syntax.ui.rtextarea.IconRowHeader; -import com.fr.design.gui.syntax.ui.rtextarea.RecordableTextAction; import com.fr.design.gui.syntax.ui.rtextarea.RTextArea; import com.fr.design.gui.syntax.ui.rtextarea.RTextAreaEditorKit; +import com.fr.design.gui.syntax.ui.rtextarea.RecordableTextAction; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JScrollPane; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.text.BadLocationException; +import javax.swing.text.Caret; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.Segment; +import javax.swing.text.TextAction; +import java.awt.Component; +import java.awt.Font; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.util.Objects; +import java.util.Stack; /** @@ -32,24 +42,24 @@ import com.fr.design.gui.syntax.ui.rtextarea.RTextAreaEditorKit; * programming-specific stuff. There are currently subclasses to handle: * *
RSyntaxTextAreaEditorKit
adds to those of
- * RTextAreaEditorKit
.
- */
- private static final Action[] defaultActions = {
- new CloseCurlyBraceAction(),
- new CloseMarkupTagAction(),
- new BeginWordAction(beginWordAction, false),
- new BeginWordAction(selectionBeginWordAction, true),
- new ChangeFoldStateAction(rstaCollapseFoldAction, true),
- new ChangeFoldStateAction(rstaExpandFoldAction, false),
- new CollapseAllFoldsAction(),
- new CopyAsRtfAction(),
- //new DecreaseFontSizeAction(),
- new DecreaseIndentAction(),
- new DeletePrevWordAction(),
- new EndAction(endAction, false),
- new EndAction(selectionEndAction, true),
- new EndWordAction(endWordAction, false),
- new EndWordAction(endWordAction, true),
- new ExpandAllFoldsAction(),
- new GoToMatchingBracketAction(),
- new InsertBreakAction(),
- //new IncreaseFontSizeAction(),
- new InsertTabAction(),
- new NextWordAction(nextWordAction, false),
- new NextWordAction(selectionNextWordAction, true),
- new PossiblyInsertTemplateAction(),
- new PreviousWordAction(previousWordAction, false),
- new PreviousWordAction(selectionPreviousWordAction, true),
- new SelectWordAction(),
- new ToggleCommentAction(),
- };
-
-
- /**
- * Constructor.
- */
- public RSyntaxTextAreaEditorKit() {
- }
-
-
- /**
- * Returns the default document used by RSyntaxTextArea
s.
- *
- * @return The document.
- */
- @Override
- public Document createDefaultDocument() {
- return new RSyntaxDocument(SyntaxConstants.SYNTAX_STYLE_NONE);
- }
-
-
- /**
- * Overridden to return a row header that is aware of folding.
- *
- * @param textArea The text area.
- * @return The icon row header.
- */
- @Override
- public IconRowHeader createIconRowHeader(RTextArea textArea) {
- return new FoldingAwareIconRowHeader((RSyntaxTextArea)textArea);
- }
-
-
- /**
- * Fetches the set of commands that can be used
- * on a text component that is using a model and
- * view produced by this kit.
- *
- * @return the command list
- */
- @Override
- public Action[] getActions() {
- return TextAction.augmentList(super.getActions(),
- RSyntaxTextAreaEditorKit.defaultActions);
- }
-
-
- /**
- * Returns localized text for an action. There's definitely a better place
- * for this functionality.
- *
- * @param key The key into the action resource bundle.
- * @return The localized text.
- */
- public static String getString(String key) {
- return com.fr.design.i18n.Toolkit.i18nText(key);
- }
-
-
- /**
- * Positions the caret at the beginning of the word. This class is here
- * to better handle finding the "beginning of the word" for programming
- * languages.
- */
- protected static class BeginWordAction
- extends RTextAreaEditorKit.BeginWordAction {
-
- private Segment seg;
-
- protected BeginWordAction(String name, boolean select) {
- super(name, select);
- seg = new Segment();
- }
-
- @Override
- protected int getWordStart(RTextArea textArea, int offs)
- throws BadLocationException {
-
- if (offs==0) {
- return offs;
- }
-
- RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
- int line = textArea.getLineOfOffset(offs);
- int start = textArea.getLineStartOffset(line);
- if (offs==start) {
- return start;
- }
- int end = textArea.getLineEndOffset(line);
- if (line!=textArea.getLineCount()-1) {
- end--;
- }
- doc.getText(start, end-start, seg);
-
- // Determine the "type" of char at offs - lower case, upper case,
- // whitespace or other. We take special care here as we're starting
- // in the middle of the Segment to check whether we're already at
- // the "beginning" of a word.
- int firstIndex = seg.getBeginIndex() + (offs-start) - 1;
- seg.setIndex(firstIndex);
- char ch = seg.current();
- char nextCh = offs==end ? 0 : seg.array[seg.getIndex() + 1];
-
- // The "word" is a group of letters and/or digits
- if (Character.isLetterOrDigit(ch)) {
- if (offs!=end && !Character.isLetterOrDigit(nextCh)) {
- return offs;
- }
- do {
- ch = seg.previous();
- } while (Character.isLetterOrDigit(ch));
- }
-
- // The "word" is whitespace
- else if (Character.isWhitespace(ch)) {
- if (offs!=end && !Character.isWhitespace(nextCh)) {
- return offs;
- }
- do {
- ch = seg.previous();
- } while (Character.isWhitespace(ch));
- }
-
- // Otherwise, the "word" a single "something else" char (operator,
- // etc.).
-
- offs -= firstIndex - seg.getIndex() + 1;//seg.getEndIndex() - seg.getIndex();
- if (ch!=Segment.DONE && nextCh!='\n') {
- offs++;
- }
-
- return offs;
-
- }
-
- }
-
-
- /**
- * Expands or collapses the nearest fold.
- */
- public static class ChangeFoldStateAction extends FoldRelatedAction {
-
- private boolean collapse;
-
- public ChangeFoldStateAction(String name, boolean collapse) {
- super(name);
- this.collapse = collapse;
- }
-
- public ChangeFoldStateAction(String name, Icon icon,
- String desc, Integer mnemonic, KeyStroke accelerator) {
- super(name, icon, desc, mnemonic, accelerator);
- }
-
- @Override
- public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
- RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
- if (rsta.isCodeFoldingEnabled()) {
- Fold fold = getClosestFold(rsta);
- if (fold!=null) {
- fold.setCollapsed(collapse);
- }
- possiblyRepaintGutter(textArea);
- }
- else {
- UIManager.getLookAndFeel().provideErrorFeedback(rsta);
- }
- }
-
- @Override
- public final String getMacroID() {
- return getName();
- }
-
- }
-
-
- /**
- * Action that (optionally) aligns a closing curly brace with the line
- * containing its matching opening curly brace.
- */
- public static class CloseCurlyBraceAction extends RecordableTextAction {
-
- private static final long serialVersionUID = 1L;
-
- private Point bracketInfo;
- private Segment seg;
-
- public CloseCurlyBraceAction() {
- super(rstaCloseCurlyBraceAction);
- seg = new Segment();
- }
-
- @Override
- public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
-
- RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
- RSyntaxDocument doc = (RSyntaxDocument)rsta.getDocument();
- boolean alignCurlyBraces = rsta.isAutoIndentEnabled() &&
- doc.getCurlyBracesDenoteCodeBlocks();
-
- if (alignCurlyBraces) {
- textArea.beginAtomicEdit();
- }
-
- try {
-
- textArea.replaceSelection("}");
-
- // If the user wants to align curly braces...
- if (alignCurlyBraces) {
-
- Element root = doc.getDefaultRootElement();
- int dot = rsta.getCaretPosition() - 1; // Start before '{'
- int line = root.getElementIndex(dot);
- Element elem = root.getElement(line);
- int start = elem.getStartOffset();
-
- // Get the current line's text up to the '}' entered.
- try {
- doc.getText(start, dot-start, seg);
- } catch (BadLocationException ble) { // Never happens
- ble.printStackTrace();
- return;
- }
-
- // Only attempt to align if there's only whitespace up to
- // the '}' entered.
- for (int i=0; i</
"
- * or "[
" in the case of BBCode).
- * @return The name of the tag to close, or null
if it
- * could not be determined.
- */
- private String discoverTagName(RSyntaxDocument doc, int dot) {
-
- Stack-1
if only
- * whitespace chars follow pos
(or it is the end
- * position in the string).
- */
- private static final int atEndOfLine(int pos, String s, int sLen) {
- for (int i=pos; iRSyntaxTextAreaEditorKit
adds to those of
+ * RTextAreaEditorKit
.
+ */
+ private static final Action[] defaultActions = {
+ new CloseCurlyBraceAction(),
+ new CloseMarkupTagAction(),
+ new BeginWordAction(beginWordAction, false),
+ new BeginWordAction(selectionBeginWordAction, true),
+ new ChangeFoldStateAction(rstaCollapseFoldAction, true),
+ new ChangeFoldStateAction(rstaExpandFoldAction, false),
+ new CollapseAllFoldsAction(),
+ new CopyAsRtfAction(),
+ //new DecreaseFontSizeAction(),
+ new DecreaseIndentAction(),
+ new DeletePrevWordAction(),
+ new EndAction(endAction, false),
+ new EndAction(selectionEndAction, true),
+ new EndWordAction(endWordAction, false),
+ new EndWordAction(endWordAction, true),
+ new ExpandAllFoldsAction(),
+ new GoToMatchingBracketAction(),
+ new InsertBreakAction(),
+ //new IncreaseFontSizeAction(),
+ new InsertTabAction(),
+ new NextWordAction(nextWordAction, false),
+ new NextWordAction(selectionNextWordAction, true),
+ new PossiblyInsertTemplateAction(),
+ new PreviousWordAction(previousWordAction, false),
+ new PreviousWordAction(selectionPreviousWordAction, true),
+ new SelectWordAction(),
+ new ToggleCommentAction(),
+ };
+
+
+ /**
+ * Constructor.
+ */
+ public RSyntaxTextAreaEditorKit() {
+ }
+
+
+ /**
+ * Returns the default document used by RSyntaxTextArea
s.
+ *
+ * @return The document.
+ */
+ @Override
+ public Document createDefaultDocument() {
+ return new RSyntaxDocument(SyntaxConstants.SYNTAX_STYLE_NONE);
+ }
+
+
+ /**
+ * Overridden to return a row header that is aware of folding.
+ *
+ * @param textArea The text area.
+ * @return The icon row header.
+ */
+ @Override
+ public IconRowHeader createIconRowHeader(RTextArea textArea) {
+ return new FoldingAwareIconRowHeader((RSyntaxTextArea) textArea);
+ }
+
+
+ /**
+ * Fetches the set of commands that can be used
+ * on a text component that is using a model and
+ * view produced by this kit.
+ *
+ * @return the command list
+ */
+ @Override
+ public Action[] getActions() {
+ return TextAction.augmentList(super.getActions(),
+ RSyntaxTextAreaEditorKit.defaultActions);
+ }
+
+
+ /**
+ * Returns localized text for an action. There's definitely a better place
+ * for this functionality.
+ *
+ * @param key The key into the action resource bundle.
+ * @return The localized text.
+ */
+ public static String getString(String key) {
+ return com.fr.design.i18n.Toolkit.i18nText(key);
+ }
+
+
+ /**
+ * Positions the caret at the beginning of the word. This class is here
+ * to better handle finding the "beginning of the word" for programming
+ * languages.
+ */
+ protected static class BeginWordAction
+ extends RTextAreaEditorKit.BeginWordAction {
+
+ private Segment seg;
+
+ protected BeginWordAction(String name, boolean select) {
+ super(name, select);
+ seg = new Segment();
+ }
+
+ @Override
+ protected int getWordStart(RTextArea textArea, int offs)
+ throws BadLocationException {
+
+ if (offs == 0) {
+ return offs;
+ }
+
+ RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
+ int line = textArea.getLineOfOffset(offs);
+ int start = textArea.getLineStartOffset(line);
+ if (offs == start) {
+ return start;
+ }
+ int end = textArea.getLineEndOffset(line);
+ if (line != textArea.getLineCount() - 1) {
+ end--;
+ }
+ doc.getText(start, end - start, seg);
+
+ // Determine the "type" of char at offs - lower case, upper case,
+ // whitespace or other. We take special care here as we're starting
+ // in the middle of the Segment to check whether we're already at
+ // the "beginning" of a word.
+ int firstIndex = seg.getBeginIndex() + (offs - start) - 1;
+ seg.setIndex(firstIndex);
+ char ch = seg.current();
+ char nextCh = offs == end ? 0 : seg.array[seg.getIndex() + 1];
+
+ // The "word" is a group of letters and/or digits
+ if (Character.isLetterOrDigit(ch)) {
+ if (offs != end && !Character.isLetterOrDigit(nextCh)) {
+ return offs;
+ }
+ do {
+ ch = seg.previous();
+ } while (Character.isLetterOrDigit(ch));
+ }
+
+ // The "word" is whitespace
+ else if (Character.isWhitespace(ch)) {
+ if (offs != end && !Character.isWhitespace(nextCh)) {
+ return offs;
+ }
+ do {
+ ch = seg.previous();
+ } while (Character.isWhitespace(ch));
+ }
+
+ // Otherwise, the "word" a single "something else" char (operator,
+ // etc.).
+
+ offs -= firstIndex - seg.getIndex() + 1;//seg.getEndIndex() - seg.getIndex();
+ if (ch != Segment.DONE && nextCh != '\n') {
+ offs++;
+ }
+
+ return offs;
+
+ }
+
+ }
+
+
+ /**
+ * Expands or collapses the nearest fold.
+ */
+ public static class ChangeFoldStateAction extends FoldRelatedAction {
+
+ private boolean collapse;
+
+ public ChangeFoldStateAction(String name, boolean collapse) {
+ super(name);
+ this.collapse = collapse;
+ }
+
+ public ChangeFoldStateAction(String name, Icon icon,
+ String desc, Integer mnemonic, KeyStroke accelerator) {
+ super(name, icon, desc, mnemonic, accelerator);
+ }
+
+ @Override
+ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
+ RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
+ if (rsta.isCodeFoldingEnabled()) {
+ Fold fold = getClosestFold(rsta);
+ if (fold != null) {
+ fold.setCollapsed(collapse);
+ }
+ possiblyRepaintGutter(textArea);
+ } else {
+ UIManager.getLookAndFeel().provideErrorFeedback(rsta);
+ }
+ }
+
+ @Override
+ public final String getMacroID() {
+ return getName();
+ }
+
+ }
+
+
+ /**
+ * Action that (optionally) aligns a closing curly brace with the line
+ * containing its matching opening curly brace.
+ */
+ public static class CloseCurlyBraceAction extends RecordableTextAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private Point bracketInfo;
+ private Segment seg;
+
+ public CloseCurlyBraceAction() {
+ super(rstaCloseCurlyBraceAction);
+ seg = new Segment();
+ }
+
+ @Override
+ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
+
+ RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
+ RSyntaxDocument doc = (RSyntaxDocument) rsta.getDocument();
+ boolean alignCurlyBraces = rsta.isAutoIndentEnabled() &&
+ doc.getCurlyBracesDenoteCodeBlocks();
+
+ if (alignCurlyBraces) {
+ textArea.beginAtomicEdit();
+ }
+
+ try {
+
+ textArea.replaceSelection("}");
+
+ // If the user wants to align curly braces...
+ if (alignCurlyBraces) {
+
+ Element root = doc.getDefaultRootElement();
+ int dot = rsta.getCaretPosition() - 1; // Start before '{'
+ int line = root.getElementIndex(dot);
+ Element elem = root.getElement(line);
+ int start = elem.getStartOffset();
+
+ // Get the current line's text up to the '}' entered.
+ try {
+ doc.getText(start, dot - start, seg);
+ } catch (BadLocationException ble) { // Never happens
+ ble.printStackTrace();
+ return;
+ }
+
+ // Only attempt to align if there's only whitespace up to
+ // the '}' entered.
+ for (int i = 0; i < seg.count; i++) {
+ char ch = seg.array[seg.offset + i];
+ if (!Character.isWhitespace(ch)) {
+ return;
+ }
+ }
+
+ // Locate the matching '{' bracket, and replace the leading
+ // whitespace for the '}' to match that of the '{' char's line.
+ bracketInfo = RSyntaxUtilities.getMatchingBracketPosition(
+ rsta, bracketInfo);
+ if (bracketInfo.y > -1) {
+ try {
+ String ws = RSyntaxUtilities.getLeadingWhitespace(
+ doc, bracketInfo.y);
+ rsta.replaceRange(ws, start, dot);
+ } catch (BadLocationException ble) {
+ ble.printStackTrace();
+ return;
+ }
+ }
+
+ }
+
+ } finally {
+ if (alignCurlyBraces) {
+ textArea.endAtomicEdit();
+ }
+ }
+
+ }
+
+ @Override
+ public final String getMacroID() {
+ return rstaCloseCurlyBraceAction;
+ }
+
+ }
+
+
+ /**
+ * (Optionally) completes a closing markup tag.
+ */
+ public static class CloseMarkupTagAction extends RecordableTextAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public CloseMarkupTagAction() {
+ super(rstaCloseMarkupTagAction);
+ }
+
+ @Override
+ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
+
+ if (!textArea.isEditable() || !textArea.isEnabled()) {
+ UIManager.getLookAndFeel().provideErrorFeedback(textArea);
+ return;
+ }
+
+ RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
+ RSyntaxDocument doc = (RSyntaxDocument) rsta.getDocument();
+
+ Caret c = rsta.getCaret();
+ boolean selection = c.getDot() != c.getMark();
+ rsta.replaceSelection("/");
+
+ // Don't automatically complete a tag if there was a selection
+ int dot = c.getDot();
+
+ if (doc.getLanguageIsMarkup() &&
+ doc.getCompleteMarkupCloseTags() &&
+ !selection && rsta.getCloseMarkupTags() && dot > 1) {
+
+ try {
+
+ // Check actual char before token type, since it's quicker
+ char ch = doc.charAt(dot - 2);
+ if (ch == '<' || ch == '[') {
+
+ Token t = doc.getTokenListForLine(
+ rsta.getCaretLineNumber());
+ t = RSyntaxUtilities.getTokenAtOffset(t, dot - 1);
+ if (t != null && t.getType() == Token.MARKUP_TAG_DELIMITER) {
+ //System.out.println("Huzzah - closing tag!");
+ String tagName = discoverTagName(doc, dot);
+ if (tagName != null) {
+ rsta.replaceSelection(tagName + (char) (ch + 2));
+ }
+ }
+
+ }
+
+ } catch (BadLocationException ble) { // Never happens
+ UIManager.getLookAndFeel().provideErrorFeedback(rsta);
+ ble.printStackTrace();
+ }
+
+ }
+
+ }
+
+ /**
+ * Discovers the name of the tag being closed. Assumes standard
+ * SGML-style markup tags.
+ *
+ * @param doc The document to parse.
+ * @param dot The location of the caret. This should be right after
+ * the start of a closing tag token (e.g. "</
"
+ * or "[
" in the case of BBCode).
+ * @return The name of the tag to close, or null
if it
+ * could not be determined.
+ */
+ private String discoverTagName(RSyntaxDocument doc, int dot) {
+
+ Stackpos
that
+ * is NOT a whitespace char, or -1
if only
+ * whitespace chars follow pos
(or it is the end
+ * position in the string).
+ */
+ private static final int atEndOfLine(int pos, String s, int sLen) {
+ for (int i = pos; i < sLen; i++) {
+ if (!RSyntaxUtilities.isWhitespace(s.charAt(i))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static final int getOpenBraceCount(RSyntaxDocument doc) {
+ int openCount = 0;
+ Element root = doc.getDefaultRootElement();
+ int lineCount = root.getElementCount();
+ for (int i = 0; i < lineCount; i++) {
+ Token t = doc.getTokenListForLine(i);
+ while (t != null && t.isPaintable()) {
+ if (t.getType() == Token.SEPARATOR && t.length() == 1) {
+ char ch = t.charAt(0);
+ if (ch == '{') {
+ openCount++;
+ } else if (ch == '}') {
+ openCount--;
+ }
+ }
+ t = t.getNextToken();
+ }
+ }
+ return openCount;
+ }
+
+ /**
+ * Actually inserts the newline into the document, and auto-indents
+ * if appropriate. This method can be called by token makers who
+ * implement a custom action for inserting newlines.
+ *
+ * @param textArea
+ * @param noSelection Whether there is no selection.
+ */
+ protected void handleInsertBreak(RSyntaxTextArea textArea,
+ boolean noSelection) {
+ // If we're auto-indenting...
+ if (noSelection && textArea.isAutoIndentEnabled()) {
+ insertNewlineWithAutoIndent(textArea);
+ } else {
+ textArea.replaceSelection("\n");
+ if (noSelection) {
+ possiblyCloseCurlyBrace(textArea, null);
+ }
+ }
+ }
+
+ private void insertNewlineWithAutoIndent(RSyntaxTextArea sta) {
+
+ try {
+
+ int caretPos = sta.getCaretPosition();
+ Document doc = sta.getDocument();
+ Element map = doc.getDefaultRootElement();
+ int lineNum = map.getElementIndex(caretPos);
+ Element line = map.getElement(lineNum);
+ int start = line.getStartOffset();
+ int end = line.getEndOffset() - 1; // Why always "-1"?
+ int len = end - start;
+ String s = doc.getText(start, len);
+
+ // endWS is the end of the leading whitespace of the
+ // current line.
+ String leadingWS = RSyntaxUtilities.getLeadingWhitespace(s);
+ StringBuilder sb = new StringBuilder("\n");
+ sb.append(leadingWS);
+
+ // If there is only whitespace between the caret and
+ // the EOL, pressing Enter auto-indents the new line to
+ // the same place as the previous line.
+ int nonWhitespacePos = atEndOfLine(caretPos - start, s, len);
+ if (nonWhitespacePos == -1) {
+ if (leadingWS.length() == len &&
+ sta.isClearWhitespaceLinesEnabled()) {
+ // If the line was nothing but whitespace, select it
+ // so its contents get removed.
+ sta.setSelectionStart(start);
+ sta.setSelectionEnd(end);
+ }
+ sta.replaceSelection(sb.toString());
+ }
+
+ // If there is non-whitespace between the caret and the
+ // EOL, pressing Enter takes that text to the next line
+ // and auto-indents it to the same place as the last
+ // line.
+ else {
+ sb.append(s.substring(nonWhitespacePos));
+ sta.replaceRange(sb.toString(), caretPos, end);
+ sta.setCaretPosition(caretPos + leadingWS.length() + 1);
+ }
+
+ // Must do it after everything else, as the "smart indent"
+ // calculation depends on the previous line's state
+ // AFTER the Enter press (stuff may have been moved down).
+ if (sta.getShouldIndentNextLine(lineNum)) {
+ sta.replaceSelection("\t");
+ }
+
+ possiblyCloseCurlyBrace(sta, leadingWS);
+
+ } catch (BadLocationException ble) { // Never happens
+ sta.replaceSelection("\n");
+ ble.printStackTrace();
+ }
+
+ }
+
+ private void possiblyCloseCurlyBrace(RSyntaxTextArea textArea,
+ String leadingWS) {
+
+ RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
+
+ if (textArea.getCloseCurlyBraces() &&
+ doc.getCurlyBracesDenoteCodeBlocks()) {
+
+ int line = textArea.getCaretLineNumber();
+ Token t = doc.getTokenListForLine(line - 1);
+ t = t.getLastNonCommentNonWhitespaceToken();
+
+ if (t != null && t.isLeftCurly()) {
+
+ if (getOpenBraceCount(doc) > 0) {
+ StringBuilder sb = new StringBuilder();
+ if (line == textArea.getLineCount() - 1) {
+ sb.append('\n');
+ }
+ if (leadingWS != null) {
+ sb.append(leadingWS);
+ }
+ sb.append("}\n");
+ int dot = textArea.getCaretPosition();
+ int end = textArea.getLineEndOffsetOfCurrentLine();
+ // Insert at end of line, not at dot: they may have
+ // pressed Enter in the middle of the line and brought
+ // some text (though it must be whitespace and/or
+ // comments) down onto the new line.
+ textArea.insert(sb.toString(), end);
+ textArea.setCaretPosition(dot); // Caret may have moved
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+
+ /**
+ * Action for inserting tabs. This is extended to "block indent" a
+ * group of contiguous lines if they are selected.
+ */
+ public static class InsertTabAction extends RecordableTextAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public InsertTabAction() {
+ super(insertTabAction);
+ }
+
+ public InsertTabAction(String name) {
+ super(name);
+ }
+
+ @Override
+ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
+
+ if (!textArea.isEditable() || !textArea.isEnabled()) {
+ UIManager.getLookAndFeel().provideErrorFeedback(textArea);
+ return;
+ }
+
+ Document document = textArea.getDocument();
+ Element map = document.getDefaultRootElement();
+ Caret c = textArea.getCaret();
+ int dot = c.getDot();
+ int mark = c.getMark();
+ int dotLine = map.getElementIndex(dot);
+ int markLine = map.getElementIndex(mark);
+
+ // If there is a multi-line selection, indent all lines in
+ // the selection.
+ if (dotLine != markLine) {
+ int first = Math.min(dotLine, markLine);
+ int last = Math.max(dotLine, markLine);
+ Element elem;
+ int start;
+
+ // Since we're using Document.insertString(), we must mimic the
+ // soft tab behavior provided by RTextArea.replaceSelection().
+ String replacement = "\t";
+ if (textArea.getTabsEmulated()) {
+ StringBuilder sb = new StringBuilder();
+ int temp = textArea.getTabSize();
+ for (int i = 0; i < temp; i++) {
+ sb.append(' ');
+ }
+ replacement = sb.toString();
+ }
+
+ textArea.beginAtomicEdit();
+ try {
+ for (int i = first; i < last; i++) {
+ elem = map.getElement(i);
+ start = elem.getStartOffset();
+ document.insertString(start, replacement, null);
+ }
+ // Don't do the last line if the caret is at its
+ // beginning. We must call getDot() again and not just
+ // use 'dot' as the caret's position may have changed
+ // due to the insertion of the tabs above.
+ elem = map.getElement(last);
+ start = elem.getStartOffset();
+ if (Math.max(c.getDot(), c.getMark()) != start) {
+ document.insertString(start, replacement, null);
+ }
+ } catch (BadLocationException ble) { // Never happens.
+ ble.printStackTrace();
+ UIManager.getLookAndFeel().
+ provideErrorFeedback(textArea);
+ } finally {
+ textArea.endAtomicEdit();
+ }
+ } else {
+ textArea.replaceSelection("\t");
+ }
+
+ }
+
+ @Override
+ public final String getMacroID() {
+ return insertTabAction;
+ }
+
+ }
+
+
+ /**
+ * Action to move the selection and/or caret. Constructor indicates
+ * direction to use. This class overrides the behavior defined in
+ * {@link RTextAreaEditorKit} to better skip "words" in source code.
+ */
+ public static class NextWordAction
+ extends RTextAreaEditorKit.NextWordAction {
+
+ private Segment seg;
+
+ public NextWordAction(String nm, boolean select) {
+ super(nm, select);
+ seg = new Segment();
+ }
+
+ /**
+ * Overridden to do better with skipping "words" in code.
+ */
+ @Override
+ protected int getNextWord(RTextArea textArea, int offs)
+ throws BadLocationException {
+
+ RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
+ if (offs == doc.getLength()) {
+ return offs;
+ }
+
+ Element root = doc.getDefaultRootElement();
+ int line = root.getElementIndex(offs);
+ int end = root.getElement(line).getEndOffset() - 1;
+ if (offs == end) {// If we're already at the end of the line...
+ RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
+ if (rsta.isCodeFoldingEnabled()) { // Start of next visible line
+ FoldManager fm = rsta.getFoldManager();
+ int lineCount = root.getElementCount();
+ while (++line < lineCount && fm.isLineHidden(line)) {
+ ;
+ }
+ if (line < lineCount) { // Found a lower visible line
+ offs = root.getElement(line).getStartOffset();
+ }
+ // No lower visible line - we're already at last visible offset
+ return offs;
+ } else {
+ return offs + 1; // Start of next line.
+ }
+ }
+ doc.getText(offs, end - offs, seg);
+
+ // Determine the "type" of char at offs - letter/digit,
+ // whitespace or other
+ char ch = seg.first();
+
+ // Skip the group of letters and/or digits
+ if (Character.isLetterOrDigit(ch)) {
+ do {
+ ch = seg.next();
+ } while (Character.isLetterOrDigit(ch));
+ }
+
+ // Skip groups of "anything else" (operators, etc.).
+ else if (!Character.isWhitespace(ch)) {
+ do {
+ ch = seg.next();
+ } while (ch != Segment.DONE &&
+ !(Character.isLetterOrDigit(ch) ||
+ Character.isWhitespace(ch)));
+ }
+
+ // Skip any trailing whitespace
+ while (Character.isWhitespace(ch)) {
+ ch = seg.next();
+ }
+
+ offs += seg.getIndex() - seg.getBeginIndex();
+ return offs;
+
+ }
+
+ }
+
+
+ /**
+ * Action for when the user tries to insert a template (that is,
+ * they've typed a template ID and pressed the trigger character
+ * (a space) in an attempt to do the substitution).
+ */
+ public static class PossiblyInsertTemplateAction extends RecordableTextAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public PossiblyInsertTemplateAction() {
+ super(rstaPossiblyInsertTemplateAction);
+ }
+
+ @Override
+ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
+
+ if (!textArea.isEditable() || !textArea.isEnabled()) {
+ return;
+ }
+
+ RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
+
+ if (RSyntaxTextArea.getTemplatesEnabled()) {
+
+ Document doc = textArea.getDocument();
+ if (doc != null) {
+
+ try {
+
+ CodeTemplateManager manager = RSyntaxTextArea.
+ getCodeTemplateManager();
+ CodeTemplate template = manager == null ? null :
+ manager.getTemplate(rsta);
+
+ // A non-null template means modify the text to insert!
+ if (template != null) {
+ template.invoke(rsta);
+ }
+
+ // No template - insert default text. This is
+ // exactly what DefaultKeyTypedAction does.
+ else {
+ doDefaultInsert(rsta);
+ }
+
+ } catch (BadLocationException ble) {
+ UIManager.getLookAndFeel().
+ provideErrorFeedback(textArea);
+ }
+
+
+ } // End of if (doc!=null).
+
+ } // End of if (textArea.getTemplatesEnabled()).
+
+ // If templates aren't enabled, just insert the text as usual.
+ else {
+ doDefaultInsert(rsta);
+ }
+
+ }
+
+ private final void doDefaultInsert(RTextArea textArea) {
+ // FIXME: We need a way to get the "trigger string" (i.e.,
+ // the text that was just typed); however, the text area's
+ // template manager might be null (if templates are disabled).
+ // Also, the manager's trigger string doesn't yet match up with
+ // that defined in RSyntaxTextAreaEditorKit.java (which is
+ // hardcoded as a space)...
+ //String str = manager.getInsertTriggerString();
+ //int mod = manager.getInsertTrigger().getModifiers();
+ //if (str!=null && str.length()>0 &&
+ // ((mod&ActionEvent.ALT_MASK)==(mod&ActionEvent.CTRL_MASK))) {
+ // char ch = str.charAt(0);
+ // if (ch>=0x20 && ch!=0x7F)
+ // textArea.replaceSelection(str);
+ //}
+ textArea.replaceSelection(" ");
+ }
+
+ @Override
+ public final String getMacroID() {
+ return rstaPossiblyInsertTemplateAction;
+ }
+
+ }
+
+
+ /**
+ * Action to move the selection and/or caret. Constructor indicates
+ * direction to use. This class overrides the behavior defined in
+ * {@link RTextAreaEditorKit} to better skip "words" in source code.
+ */
+ public static class PreviousWordAction
+ extends RTextAreaEditorKit.PreviousWordAction {
+
+ private Segment seg;
+
+ public PreviousWordAction(String nm, boolean select) {
+ super(nm, select);
+ seg = new Segment();
+ }
+
+ /**
+ * Overridden to do better with skipping "words" in code.
+ */
+ @Override
+ protected int getPreviousWord(RTextArea textArea, int offs)
+ throws BadLocationException {
+
+ if (offs == 0) {
+ return offs;
+ }
+
+ RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
+ Element root = doc.getDefaultRootElement();
+ int line = root.getElementIndex(offs);
+ int start = root.getElement(line).getStartOffset();
+ if (offs == start) {// If we're already at the start of the line...
+ RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
+ if (rsta.isCodeFoldingEnabled()) { // End of next visible line
+ FoldManager fm = rsta.getFoldManager();
+ while (--line >= 0 && fm.isLineHidden(line)) {
+ ;
+ }
+ if (line >= 0) { // Found an earlier visible line
+ offs = root.getElement(line).getEndOffset() - 1;
+ }
+ // No earlier visible line - we must be at offs==0...
+ return offs;
+ } else {
+ return start - 1; // End of previous line.
+ }
+ }
+ doc.getText(start, offs - start, seg);
+
+ // Determine the "type" of char at offs - lower case, upper case,
+ // whitespace or other
+ char ch = seg.last();
+
+ // Skip any "leading" whitespace
+ while (Character.isWhitespace(ch)) {
+ ch = seg.previous();
+ }
+
+ // Skip the group of letters and/or digits
+ if (Character.isLetterOrDigit(ch)) {
+ do {
+ ch = seg.previous();
+ } while (Character.isLetterOrDigit(ch));
+ }
+
+ // Skip groups of "anything else" (operators, etc.).
+ else if (!Character.isWhitespace(ch)) {
+ do {
+ ch = seg.previous();
+ } while (ch != Segment.DONE &&
+ !(Character.isLetterOrDigit(ch) ||
+ Character.isWhitespace(ch)));
+ }
+
+ offs -= seg.getEndIndex() - seg.getIndex();
+ if (ch != Segment.DONE) {
+ offs++;
+ }
+
+ return offs;
+
+ }
+
+ }
+
+
+ /**
+ * Selects the word around the caret. This class is here to better
+ * handle selecting "words" in programming languages.
+ */
+ public static class SelectWordAction
+ extends RTextAreaEditorKit.SelectWordAction {
+
+ @Override
+ protected void createActions() {
+ start = new BeginWordAction("pigdog", false);
+ end = new EndWordAction("pigdog", true);
+ }
+
+ }
+
+
+ /**
+ * Action that toggles whether the currently selected lines are
+ * commented.
+ */
+ public static class ToggleCommentAction extends RecordableTextAction {
+
+ public ToggleCommentAction() {
+ super(rstaToggleCommentAction);
+ }
+
+ @Override
+ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
+
+ if (!textArea.isEditable() || !textArea.isEnabled()) {
+ UIManager.getLookAndFeel().provideErrorFeedback(textArea);
+ return;
+ }
+
+ RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
+ String[] startEnd = doc.getLineCommentStartAndEnd();
+
+ if (startEnd == null) {
+ UIManager.getLookAndFeel().provideErrorFeedback(textArea);
+ return;
+ }
+
+ Element map = doc.getDefaultRootElement();
+ Caret c = textArea.getCaret();
+ int dot = c.getDot();
+ int mark = c.getMark();
+ int line1 = map.getElementIndex(dot);
+ int line2 = map.getElementIndex(mark);
+ int start = Math.min(line1, line2);
+ int end = Math.max(line1, line2);
+
+ // Don't toggle comment on last line if there is no
+ // text selected on it.
+ if (start != end) {
+ Element elem = map.getElement(end);
+ if (Math.max(dot, mark) == elem.getStartOffset()) {
+ end--;
+ }
+ }
+
+ textArea.beginAtomicEdit();
+ try {
+ boolean add = getDoAdd(doc, map, start, end, startEnd);
+ for (line1 = start; line1 <= end; line1++) {
+ Element elem = map.getElement(line1);
+ handleToggleComment(elem, doc, startEnd, add);
+ }
+ } catch (BadLocationException ble) {
+ ble.printStackTrace();
+ UIManager.getLookAndFeel().provideErrorFeedback(textArea);
+ } finally {
+ textArea.endAtomicEdit();
+ }
+
+ }
+
+ private boolean getDoAdd(Document doc, Element map, int startLine,
+ int endLine, String[] startEnd)
+ throws BadLocationException {
+ boolean doAdd = false;
+ for (int i = startLine; i <= endLine; i++) {
+ Element elem = map.getElement(i);
+ int start = elem.getStartOffset();
+ String t = doc.getText(start, elem.getEndOffset() - start - 1);
+ if (!t.startsWith(startEnd[0]) ||
+ (startEnd[1] != null && !t.endsWith(startEnd[1]))) {
+ doAdd = true;
+ break;
+ }
+ }
+ return doAdd;
+ }
+
+ private void handleToggleComment(Element elem, Document doc,
+ String[] startEnd, boolean add) throws BadLocationException {
+ int start = elem.getStartOffset();
+ int end = elem.getEndOffset() - 1;
+ if (add) {
+ doc.insertString(start, startEnd[0], null);
+ if (startEnd[1] != null) {
+ doc.insertString(end + startEnd[0].length(), startEnd[1],
+ null);
+ }
+ } else {
+ doc.remove(start, startEnd[0].length());
+ if (startEnd[1] != null) {
+ int temp = startEnd[1].length();
+ doc.remove(end - startEnd[0].length() - temp, temp);
+ }
+ }
+ }
+
+ @Override
+ public final String getMacroID() {
+ return rstaToggleCommentAction;
+ }
+
+ }
+
+
+ /**
+ * Toggles the fold at the current caret position or line.
+ */
+ public static class ToggleCurrentFoldAction extends FoldRelatedAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public ToggleCurrentFoldAction() {
+ super(rstaToggleCurrentFoldAction);
+ setProperties("Action.ToggleCurrentFold");
+ }
+
+ public ToggleCurrentFoldAction(String name, Icon icon, String desc,
+ Integer mnemonic, KeyStroke accelerator) {
+ super(name, icon, desc, mnemonic, accelerator);
+ }
+
+ @Override
+ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
+ RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
+ if (rsta.isCodeFoldingEnabled()) {
+ Fold fold = getClosestFold(rsta);
+ if (fold != null) {
+ fold.toggleCollapsedState();
+ }
+ possiblyRepaintGutter(textArea);
+ } else {
+ UIManager.getLookAndFeel().provideErrorFeedback(rsta);
+ }
+ }
+
+ @Override
+ public final String getMacroID() {
+ return rstaToggleCurrentFoldAction;
+ }
+
+ }
}
\ No newline at end of file
diff --git a/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rsyntaxtextarea/RSyntaxUtilities.java b/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rsyntaxtextarea/RSyntaxUtilities.java
index 87be45892..ddb3ddfa3 100644
--- a/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rsyntaxtextarea/RSyntaxUtilities.java
+++ b/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rsyntaxtextarea/RSyntaxUtilities.java
@@ -3,12 +3,30 @@
*
* RSyntaxUtilities.java - Utility methods used by RSyntaxTextArea and its
* views.
- *
+ *
* This library is distributed under a modified BSD license. See the included
* RSyntaxTextArea.License.txt file for details.
*/
package com.fr.design.gui.syntax.ui.rsyntaxtextarea;
+import com.fr.design.gui.syntax.ui.rsyntaxtextarea.TokenUtils.TokenSubList;
+import com.fr.design.gui.syntax.ui.rsyntaxtextarea.folding.FoldManager;
+import com.fr.design.gui.syntax.ui.rtextarea.Gutter;
+import com.fr.design.gui.syntax.ui.rtextarea.RTextArea;
+import com.fr.design.gui.syntax.ui.rtextarea.RTextScrollPane;
+
+import javax.swing.JLabel;
+import javax.swing.JViewport;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Caret;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.Position;
+import javax.swing.text.Segment;
+import javax.swing.text.TabExpander;
+import javax.swing.text.View;
import java.awt.Color;
import java.awt.Container;
import java.awt.Point;
@@ -16,23 +34,9 @@ import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit;
import java.util.Map;
+import java.util.Objects;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
-import javax.swing.*;
-import javax.swing.text.BadLocationException;
-import javax.swing.text.Caret;
-import javax.swing.text.Document;
-import javax.swing.text.Element;
-import javax.swing.text.Position;
-import javax.swing.text.Segment;
-import javax.swing.text.TabExpander;
-import javax.swing.text.View;
-
-import com.fr.design.gui.syntax.ui.rsyntaxtextarea.TokenUtils.TokenSubList;
-import com.fr.design.gui.syntax.ui.rsyntaxtextarea.folding.FoldManager;
-import com.fr.design.gui.syntax.ui.rtextarea.Gutter;
-import com.fr.design.gui.syntax.ui.rtextarea.RTextArea;
-import com.fr.design.gui.syntax.ui.rtextarea.RTextScrollPane;
/**
@@ -44,1267 +48,1276 @@ import com.fr.design.gui.syntax.ui.rtextarea.RTextScrollPane;
*/
public class RSyntaxUtilities implements SwingConstants {
- /**
- * Integer constant representing a Windows-variant OS.
- */
- public static final int OS_WINDOWS = 1;
-
- /**
- * Integer constant representing Mac OS X.
- */
- public static final int OS_MAC_OSX = 2;
-
- /**
- * Integer constant representing Linux.
- */
- public static final int OS_LINUX = 4;
-
- /**
- * Integer constant representing an "unknown" OS. 99.99% of the
- * time, this means some UNIX variant (AIX, SunOS, etc.).
- */
- public static final int OS_OTHER = 8;
-
- /**
- * Used for the color of hyperlinks when a LookAndFeel uses light text
- * against a dark background.
- */
- private static final Color LIGHT_HYPERLINK_FG = new Color(0xd8ffff);
-
- private static final int OS = getOSImpl();
-
- //private static final int DIGIT_MASK = 1;
- private static final int LETTER_MASK = 2;
- //private static final int WHITESPACE_MASK = 4;
- //private static final int UPPER_CASE_MASK = 8;
- private static final int HEX_CHARACTER_MASK = 16;
- private static final int LETTER_OR_DIGIT_MASK = 32;
- private static final int BRACKET_MASK = 64;
- private static final int JAVA_OPERATOR_MASK = 128;
-
- /**
- * A lookup table used to quickly decide if a 16-bit Java char is a
- * US-ASCII letter (A-Z or a-z), a digit, a whitespace char (either space
- * (0x0020) or tab (0x0009)), etc. This method should be faster
- * than Character.isLetter
, Character.isDigit
,
- * and Character.isWhitespace
because we know we are dealing
- * with ASCII chars and so don't have to worry about code planes, etc.
- */
- private static final int[] dataTable = {
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, // 0-15
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
- 4, 128, 0, 0, 0, 128, 128, 0, 64, 64, 128, 128, 0, 128, 0, 128, // 32-47
- 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 128, 0, 128, 128, 128, 128, // 48-63
- 0, 58, 58, 58, 58, 58, 58, 42, 42, 42, 42, 42, 42, 42, 42, 42, // 64-79
- 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 64, 0, 64, 128, 0, // 80-95
- 0, 50, 50, 50, 50, 50, 50, 34, 34, 34, 34, 34, 34, 34, 34, 34, // 96-111
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 64, 128, 64, 128, 0, // 112-127
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128-143
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144-
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176-
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192-
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208-
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224-
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240-255.
- };
-
- /**
- * Used in bracket matching methods.
- */
- private static Segment charSegment = new Segment();
-
- /**
- * Used in token list manipulation methods.
- */
- private static final TokenImpl tempToken = new TokenImpl();
-
- /**
- * Used internally.
- */
- private static final char[] JS_KEYWORD_RETURN = { 'r', 'e', 't', 'u', 'r', 'n' };
-
- /**
- * Used internally.
- */
- private static final String BRACKETS = "{([})]";
-
-
- /**
- * Returns a string with characters that are special to HTML (such as
- * <
, >
and &
) replaced
- * by their HTML escape sequences.
- *
- * @param s The input string.
- * @param newlineReplacement What to replace newline characters with.
- * If this is null
, they are simply removed.
- * @param inPreBlock Whether this HTML will be in within pre
- * tags. If this is true
, spaces will be kept as-is;
- * otherwise, they will be converted to "
".
- * @return The escaped version of s
.
- */
- public static final String escapeForHtml(String s,
- String newlineReplacement, boolean inPreBlock) {
-
- if (s==null) {
- return null;
- }
- if (newlineReplacement==null) {
- newlineReplacement = "";
- }
- final String tabString = " ";
- boolean lastWasSpace = false;
-
- StringBuilder sb = new StringBuilder();
-
- for (int i=0; inull
if they cannot be
- * determined.
- */
- public static Map,?> getDesktopAntiAliasHints() {
- return (Map,?>)Toolkit.getDefaultToolkit().
- getDesktopProperty("awt.font.desktophints");
- }
-
-
- /**
- * Returns the color to use for the line underneath a folded region line.
- *
- * @param textArea The text area.
- * @return The color to use.
- */
- public static Color getFoldedLineBottomColor(RSyntaxTextArea textArea) {
- Color color = Color.gray;
- Gutter gutter = RSyntaxUtilities.getGutter(textArea);
- if (gutter!=null) {
- color = gutter.getFoldIndicatorForeground();
- }
- return color;
- }
-
-
- /**
- * Returns the gutter component of the scroll pane containing a text
- * area, if any.
- *
- * @param textArea The text area.
- * @return The gutter, or null
if the text area is not in
- * an {@link RTextScrollPane}.
- * @see RTextScrollPane#getGutter()
- */
- public static Gutter getGutter(RTextArea textArea) {
- Gutter gutter = null;
- Container parent = textArea.getParent();
- if (parent instanceof JViewport) {
- parent = parent.getParent();
- if (parent instanceof RTextScrollPane) {
- RTextScrollPane sp = (RTextScrollPane)parent;
- gutter = sp.getGutter(); // Should always be non-null
- }
- }
- return gutter;
- }
-
-
- /**
- * Returns the color to use for hyperlink-style components. This method
- * will return Color.blue
unless it appears that the current
- * LookAndFeel uses light text on a dark background, in which case a
- * brighter alternative is returned.
- *
- * @return The color to use for hyperlinks.
- * @see #isLightForeground(Color)
- */
- public static final Color getHyperlinkForeground() {
-
- // This property is defined by all standard LaFs, even Nimbus (!),
- // but you never know what crazy LaFs there are...
- Color fg = UIManager.getColor("Label.foreground");
- if (fg==null) {
- fg = new JLabel().getForeground();
- }
-
- return isLightForeground(fg) ? LIGHT_HYPERLINK_FG : Color.blue;
-
- }
-
-
- /**
- * Returns the leading whitespace of a string.
- *
- * @param text The String to check.
- * @return The leading whitespace.
- * @see #getLeadingWhitespace(Document, int)
- */
- public static String getLeadingWhitespace(String text) {
- int count = 0;
- int len = text.length();
- while (countSegment
for each
- * call.
- * @param p0 The starting position in the physical line in the document.
- * @param p1 The position for which to get the bounding box in the view.
- * @param e How to expand tabs.
- * @param rect The rectangle whose x- and width-values are changed to
- * represent the bounding box of p1
. This is reused
- * to keep from needlessly reallocating Rectangles.
- * @param x0 The x-coordinate (pixel) marking the left-hand border of the
- * text. This is useful if the text area has a border, for example.
- * @return The bounding box in the view of the character p1
.
- * @throws BadLocationException If p0
or p1
is
- * not a valid location in the specified text area's document.
- * @throws IllegalArgumentException If p0
and p1
- * are not on the same line.
- */
- public static Rectangle getLineWidthUpTo(RSyntaxTextArea textArea,
- Segment s, int p0, int p1,
- TabExpander e, Rectangle rect,
- int x0)
- throws BadLocationException {
-
- RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
-
- // Ensure p0 and p1 are valid document positions.
- if (p0<0)
- throw new BadLocationException("Invalid document position", p0);
- else if (p1>doc.getLength())
- throw new BadLocationException("Invalid document position", p1);
-
- // Ensure p0 and p1 are in the same line, and get the start/end
- // offsets for that line.
- Element map = doc.getDefaultRootElement();
- int lineNum = map.getElementIndex(p0);
- // We do ">1" because p1 might be the first position on the next line
- // or the last position on the previous one.
- // if (lineNum!=map.getElementIndex(p1))
- if (Math.abs(lineNum-map.getElementIndex(p1))>1)
- throw new IllegalArgumentException("p0 and p1 are not on the " +
- "same line (" + p0 + ", " + p1 + ").");
-
- // Get the token list.
- Token t = doc.getTokenListForLine(lineNum);
-
- // Modify the token list 't' to begin at p0 (but still have correct
- // token types, etc.), and get the x-location (in pixels) of the
- // beginning of this new token list.
- TokenSubList subList = TokenUtils.getSubTokenList(t, p0, e, textArea,
- 0, tempToken);
- t = subList.tokenList;
-
- rect = t.listOffsetToView(textArea, e, p1, x0, rect);
- return rect;
-
- }
-
-
- /**
- * Returns the location of the bracket paired with the one at the current
- * caret position.
- *
- * @param textArea The text area.
- * @param input A point to use as the return value. If this is
- * null
, a new object is created and returned.
- * @return A point representing the matched bracket info. The "x" field
- * is the offset of the bracket at the caret position (either just
- * before or just after the caret), and the "y" field is the offset
- * of the matched bracket. Both "x" and "y" will be
- * -1
if there isn't a matching bracket (or the caret
- * isn't on a bracket).
- */
- public static Point getMatchingBracketPosition(RSyntaxTextArea textArea,
- Point input) {
-
- if (input==null) {
- input = new Point();
- }
- input.setLocation(-1, -1);
-
- try {
-
- // Actually position just BEFORE caret.
- int caretPosition = textArea.getCaretPosition() - 1;
- RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
- char bracket = 0;
-
- // If the caret was at offset 0, we can't check "to its left."
- if (caretPosition>=0) {
- bracket = doc.charAt(caretPosition);
- }
-
- // Try to match a bracket "to the right" of the caret if one
- // was not found on the left.
- int index = BRACKETS.indexOf(bracket);
- if (index==-1 && caretPositiont
).
- * @return The next non-whitespace, non-comment token, or null
- * if there isn't one.
- * @see #getPreviousImportantToken(RSyntaxTextArea, int)
- */
- public static final Token getNextImportantToken(Token t,
- RSyntaxTextArea textArea, int line) {
- while (t!=null && t.isPaintable() && t.isCommentOrWhitespace()) {
- t = t.getNextToken();
- }
- if ((t==null || !t.isPaintable()) && linejavax.swing.text.View
is an instance of
- * {@link TokenOrientedView} and javax.swing.text.TabExpander
;
- * otherwise, a ClassCastException
could be thrown.
- *
- * @param pos the position to convert >= 0
- * @param a the allocated region in which to render
- * @param direction the direction from the current position that can
- * be thought of as the arrow keys typically found on a keyboard.
- * This will be one of the following values:
- * direction
- * doesn't have one of the legal values above
- */
- public static int getNextVisualPositionFrom(int pos, Position.Bias b,
- Shape a, int direction,
- Position.Bias[] biasRet, View view)
- throws BadLocationException {
-
- RSyntaxTextArea target = (RSyntaxTextArea)view.getContainer();
- biasRet[0] = Position.Bias.Forward;
-
- // Do we want the "next position" above, below, to the left or right?
- switch (direction) {
-
- case NORTH:
- case SOUTH:
- if (pos == -1) {
- pos = (direction == NORTH) ?
- Math.max(0, view.getEndOffset() - 1) :
- view.getStartOffset();
- break;
- }
- Caret c = (target != null) ? target.getCaret() : null;
- // YECK! Ideally, the x location from the magic caret
- // position would be passed in.
- Point mcp;
- if (c != null)
- mcp = c.getMagicCaretPosition();
- else
- mcp = null;
- int x;
- if (mcp == null) {
- Rectangle loc = target.modelToView(pos);
- x = (loc == null) ? 0 : loc.x;
- }
- else {
- x = mcp.x;
- }
- if (direction == NORTH)
- pos = getPositionAbove(target,pos,x,(TabExpander)view);
- else
- pos = getPositionBelow(target,pos,x,(TabExpander)view);
- break;
-
- case WEST:
- if(pos == -1) {
- pos = Math.max(0, view.getEndOffset() - 1);
- }
- else {
- pos = Math.max(0, pos - 1);
- if (target.isCodeFoldingEnabled()) {
- int last = target.getLineOfOffset(pos+1);
- int current = target.getLineOfOffset(pos);
- if (last!=current) { // If moving up a line...
- FoldManager fm = target.getFoldManager();
- if (fm.isLineHidden(current)) {
- while (--current>0 && fm.isLineHidden(current));
- pos = target.getLineEndOffset(current) - 1;
- }
- }
- }
- }
- break;
-
- case EAST:
- if(pos == -1) {
- pos = view.getStartOffset();
- }
- else {
- pos = Math.min(pos + 1, view.getDocument().getLength());
- if (target.isCodeFoldingEnabled()) {
- int last = target.getLineOfOffset(pos-1);
- int current = target.getLineOfOffset(pos);
- if (last!=current) { // If moving down a line...
- FoldManager fm = target.getFoldManager();
- if (fm.isLineHidden(current)) {
- int lineCount = target.getLineCount();
- while (++currentCharacter.isLetter
, Character.isDigit
,
+ * and Character.isWhitespace
because we know we are dealing
+ * with ASCII chars and so don't have to worry about code planes, etc.
+ */
+ private static final int[] dataTable = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, // 0-15
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
+ 4, 128, 0, 0, 0, 128, 128, 0, 64, 64, 128, 128, 0, 128, 0, 128, // 32-47
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 128, 0, 128, 128, 128, 128, // 48-63
+ 0, 58, 58, 58, 58, 58, 58, 42, 42, 42, 42, 42, 42, 42, 42, 42, // 64-79
+ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 64, 0, 64, 128, 0, // 80-95
+ 0, 50, 50, 50, 50, 50, 50, 34, 34, 34, 34, 34, 34, 34, 34, 34, // 96-111
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 64, 128, 64, 128, 0, // 112-127
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128-143
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144-
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176-
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192-
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208-
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224-
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240-255.
+ };
+
+ /**
+ * Used in bracket matching methods.
+ */
+ private static Segment charSegment = new Segment();
+
+ /**
+ * Used in token list manipulation methods.
+ */
+ private static final TokenImpl tempToken = new TokenImpl();
+
+ /**
+ * Used internally.
+ */
+ private static final char[] JS_KEYWORD_RETURN = {'r', 'e', 't', 'u', 'r', 'n'};
+
+ /**
+ * Used internally.
+ */
+ private static final String BRACKETS = "{([})]";
+
+
+ /**
+ * Returns a string with characters that are special to HTML (such as
+ * <
, >
and &
) replaced
+ * by their HTML escape sequences.
+ *
+ * @param s The input string.
+ * @param newlineReplacement What to replace newline characters with.
+ * If this is null
, they are simply removed.
+ * @param inPreBlock Whether this HTML will be in within pre
+ * tags. If this is true
, spaces will be kept as-is;
+ * otherwise, they will be converted to "
".
+ * @return The escaped version of s
.
+ */
+ public static final String escapeForHtml(String s,
+ String newlineReplacement, boolean inPreBlock) {
+
+ if (s == null) {
+ return null;
+ }
+ if (newlineReplacement == null) {
+ newlineReplacement = "";
+ }
+ final String tabString = " ";
+ boolean lastWasSpace = false;
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ switch (ch) {
+ case ' ':
+ if (inPreBlock || !lastWasSpace) {
+ sb.append(' ');
+ } else {
+ sb.append(" ");
+ }
+ lastWasSpace = true;
+ break;
+ case '\n':
+ sb.append(newlineReplacement);
+ lastWasSpace = false;
+ break;
+ case '&':
+ sb.append("&");
+ lastWasSpace = false;
+ break;
+ case '\t':
+ sb.append(tabString);
+ lastWasSpace = false;
+ break;
+ case '<':
+ sb.append("<");
+ lastWasSpace = false;
+ break;
+ case '>':
+ sb.append(">");
+ lastWasSpace = false;
+ break;
+ default:
+ sb.append(ch);
+ lastWasSpace = false;
+ break;
+ }
+ }
+
+ return sb.toString();
+
+ }
+
+
+ /**
+ * Returns the rendering hints for text that will most accurately reflect
+ * those of the native windowing system.
+ *
+ * @return The rendering hints, or null
if they cannot be
+ * determined.
+ */
+ public static Map, ?> getDesktopAntiAliasHints() {
+ return (Map, ?>) Toolkit.getDefaultToolkit().
+ getDesktopProperty("awt.font.desktophints");
+ }
+
+
+ /**
+ * Returns the color to use for the line underneath a folded region line.
+ *
+ * @param textArea The text area.
+ * @return The color to use.
+ */
+ public static Color getFoldedLineBottomColor(RSyntaxTextArea textArea) {
+ Color color = Color.gray;
+ Gutter gutter = RSyntaxUtilities.getGutter(textArea);
+ if (gutter != null) {
+ color = gutter.getFoldIndicatorForeground();
+ }
+ return color;
+ }
+
+
+ /**
+ * Returns the gutter component of the scroll pane containing a text
+ * area, if any.
+ *
+ * @param textArea The text area.
+ * @return The gutter, or null
if the text area is not in
+ * an {@link RTextScrollPane}.
+ * @see RTextScrollPane#getGutter()
+ */
+ public static Gutter getGutter(RTextArea textArea) {
+ Gutter gutter = null;
+ Container parent = textArea.getParent();
+ if (parent instanceof JViewport) {
+ parent = parent.getParent();
+ if (parent instanceof RTextScrollPane) {
+ RTextScrollPane sp = (RTextScrollPane) parent;
+ gutter = sp.getGutter(); // Should always be non-null
+ }
+ }
+ return gutter;
+ }
+
+
+ /**
+ * Returns the color to use for hyperlink-style components. This method
+ * will return Color.blue
unless it appears that the current
+ * LookAndFeel uses light text on a dark background, in which case a
+ * brighter alternative is returned.
+ *
+ * @return The color to use for hyperlinks.
+ * @see #isLightForeground(Color)
+ */
+ public static final Color getHyperlinkForeground() {
+
+ // This property is defined by all standard LaFs, even Nimbus (!),
+ // but you never know what crazy LaFs there are...
+ Color fg = UIManager.getColor("Label.foreground");
+ if (fg == null) {
+ fg = new JLabel().getForeground();
+ }
+
+ return isLightForeground(fg) ? LIGHT_HYPERLINK_FG : Color.blue;
+
+ }
+
+
+ /**
+ * Returns the leading whitespace of a string.
+ *
+ * @param text The String to check.
+ * @return The leading whitespace.
+ * @see #getLeadingWhitespace(Document, int)
+ */
+ public static String getLeadingWhitespace(String text) {
+ int count = 0;
+ int len = text.length();
+ while (count < len && RSyntaxUtilities.isWhitespace(text.charAt(count))) {
+ count++;
+ }
+ return text.substring(0, count);
+ }
+
+
+ /**
+ * Returns the leading whitespace of a specific line in a document.
+ *
+ * @param doc The document.
+ * @param offs The offset whose line to get the leading whitespace for.
+ * @return The leading whitespace.
+ * @throws BadLocationException If offs
is not a valid offset
+ * in the document.
+ * @see #getLeadingWhitespace(String)
+ */
+ public static String getLeadingWhitespace(Document doc, int offs)
+ throws BadLocationException {
+ Element root = doc.getDefaultRootElement();
+ int line = root.getElementIndex(offs);
+ Element elem = root.getElement(line);
+ int startOffs = elem.getStartOffset();
+ int endOffs = elem.getEndOffset() - 1;
+ String text = doc.getText(startOffs, endOffs - startOffs);
+ return getLeadingWhitespace(text);
+ }
+
+
+ private static final Element getLineElem(Document d, int offs) {
+ Element map = d.getDefaultRootElement();
+ int index = map.getElementIndex(offs);
+ Element elem = map.getElement(index);
+ if ((offs >= elem.getStartOffset()) && (offs < elem.getEndOffset())) {
+ return elem;
+ }
+ return null;
+ }
+
+
+ /**
+ * Returns the bounding box (in the current view) of a specified position
+ * in the model. This method is designed for line-wrapped views to use,
+ * as it allows you to specify a "starting position" in the line, from
+ * which the x-value is assumed to be zero. The idea is that you specify
+ * the first character in a physical line as p0
, as this is
+ * the character where the x-pixel value is 0.
+ *
+ * @param textArea The text area containing the text.
+ * @param s A segment in which to load the line. This is passed in so we
+ * don't have to reallocate a new Segment
for each
+ * call.
+ * @param p0 The starting position in the physical line in the document.
+ * @param p1 The position for which to get the bounding box in the view.
+ * @param e How to expand tabs.
+ * @param rect The rectangle whose x- and width-values are changed to
+ * represent the bounding box of p1
. This is reused
+ * to keep from needlessly reallocating Rectangles.
+ * @param x0 The x-coordinate (pixel) marking the left-hand border of the
+ * text. This is useful if the text area has a border, for example.
+ * @return The bounding box in the view of the character p1
.
+ * @throws BadLocationException If p0
or p1
is
+ * not a valid location in the specified text area's document.
+ * @throws IllegalArgumentException If p0
and p1
+ * are not on the same line.
+ */
+ public static Rectangle getLineWidthUpTo(RSyntaxTextArea textArea,
+ Segment s, int p0, int p1,
+ TabExpander e, Rectangle rect,
+ int x0)
+ throws BadLocationException {
+
+ RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
+
+ // Ensure p0 and p1 are valid document positions.
+ if (p0 < 0) {
+ throw new BadLocationException("Invalid document position", p0);
+ } else if (p1 > doc.getLength()) {
+ throw new BadLocationException("Invalid document position", p1);
+ }
+
+ // Ensure p0 and p1 are in the same line, and get the start/end
+ // offsets for that line.
+ Element map = doc.getDefaultRootElement();
+ int lineNum = map.getElementIndex(p0);
+ // We do ">1" because p1 might be the first position on the next line
+ // or the last position on the previous one.
+ // if (lineNum!=map.getElementIndex(p1))
+ if (Math.abs(lineNum - map.getElementIndex(p1)) > 1) {
+ throw new IllegalArgumentException("p0 and p1 are not on the " +
+ "same line (" + p0 + ", " + p1 + ").");
+ }
+
+ // Get the token list.
+ Token t = doc.getTokenListForLine(lineNum);
+
+ // Modify the token list 't' to begin at p0 (but still have correct
+ // token types, etc.), and get the x-location (in pixels) of the
+ // beginning of this new token list.
+ TokenSubList subList = TokenUtils.getSubTokenList(t, p0, e, textArea,
+ 0, tempToken);
+ t = subList.tokenList;
+
+ rect = t.listOffsetToView(textArea, e, p1, x0, rect);
+ return rect;
+
+ }
+
+
+ /**
+ * Returns the location of the bracket paired with the one at the current
+ * caret position.
+ *
+ * @param textArea The text area.
+ * @param input A point to use as the return value. If this is
+ * null
, a new object is created and returned.
+ * @return A point representing the matched bracket info. The "x" field
+ * is the offset of the bracket at the caret position (either just
+ * before or just after the caret), and the "y" field is the offset
+ * of the matched bracket. Both "x" and "y" will be
+ * -1
if there isn't a matching bracket (or the caret
+ * isn't on a bracket).
+ */
+ public static Point getMatchingBracketPosition(RSyntaxTextArea textArea,
+ Point input) {
+
+ if (input == null) {
+ input = new Point();
+ }
+ input.setLocation(-1, -1);
+
+ try {
+
+ // Actually position just BEFORE caret.
+ int caretPosition = textArea.getCaretPosition() - 1;
+ RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
+ char bracket = 0;
+
+ // If the caret was at offset 0, we can't check "to its left."
+ if (caretPosition >= 0) {
+ bracket = doc.charAt(caretPosition);
+ }
+
+ // Try to match a bracket "to the right" of the caret if one
+ // was not found on the left.
+ int index = BRACKETS.indexOf(bracket);
+ if (index == -1 && caretPosition < doc.getLength() - 1) {
+ bracket = doc.charAt(++caretPosition);
+ }
+
+ // First, see if the char was a bracket (one of "{[()]}").
+ if (index == -1) {
+ index = BRACKETS.indexOf(bracket);
+ if (index == -1) {
+ return input;
+ }
+ }
+
+ // If it was, then make sure this bracket isn't sitting in
+ // the middle of a comment or string. If it isn't, then
+ // initialize some stuff so we can continue on.
+ char bracketMatch;
+ boolean goForward;
+ Element map = doc.getDefaultRootElement();
+ int curLine = map.getElementIndex(caretPosition);
+ Element line = map.getElement(curLine);
+ int start = line.getStartOffset();
+ int end = line.getEndOffset();
+ Token token = doc.getTokenListForLine(curLine);
+ token = RSyntaxUtilities.getTokenAtOffset(token, caretPosition);
+ // All brackets are always returned as "separators."
+ if (Objects.requireNonNull(token).getType() != Token.SEPARATOR) {
+ return input;
+ }
+ if (index < 3) { // One of "{[("
+ goForward = true;
+ bracketMatch = BRACKETS.charAt(index + 3);
+ } else { // One of ")]}"
+ goForward = false;
+ bracketMatch = BRACKETS.charAt(index - 3);
+ }
+
+ if (goForward) {
+
+ int lastLine = map.getElementCount();
+
+ // Start just after the found bracket since we're sure
+ // we're not in a comment.
+ start = caretPosition + 1;
+ int numEmbedded = 0;
+ boolean haveTokenList = false;
+
+ while (true) {
+
+ doc.getText(start, end - start, charSegment);
+ int segOffset = charSegment.offset;
+
+ for (int i = segOffset; i < segOffset + charSegment.count; i++) {
+
+ char ch = charSegment.array[i];
+
+ if (ch == bracket) {
+ if (!haveTokenList) {
+ token = doc.getTokenListForLine(curLine);
+ haveTokenList = true;
+ }
+ int offset = start + (i - segOffset);
+ token = RSyntaxUtilities.getTokenAtOffset(token, offset);
+ if (Objects.requireNonNull(token).getType() == Token.SEPARATOR) {
+ numEmbedded++;
+ }
+ } else if (ch == bracketMatch) {
+ if (!haveTokenList) {
+ token = doc.getTokenListForLine(curLine);
+ haveTokenList = true;
+ }
+ int offset = start + (i - segOffset);
+ token = RSyntaxUtilities.getTokenAtOffset(token, offset);
+ if (Objects.requireNonNull(token).getType() == Token.SEPARATOR) {
+ if (numEmbedded == 0) {
+ if (textArea.isCodeFoldingEnabled() &&
+ textArea.getFoldManager().isLineHidden(curLine)) {
+ return input; // Match hidden in a fold
+ }
+ input.setLocation(caretPosition, offset);
+ return input;
+ }
+ numEmbedded--;
+ }
+ }
+
+ } // End of for (int i=segOffset; it
).
+ * @return The next non-whitespace, non-comment token, or null
+ * if there isn't one.
+ * @see #getPreviousImportantToken(RSyntaxTextArea, int)
+ */
+ public static final Token getNextImportantToken(Token t,
+ RSyntaxTextArea textArea, int line) {
+ while (t != null && t.isPaintable() && t.isCommentOrWhitespace()) {
+ t = t.getNextToken();
+ }
+ if ((t == null || !t.isPaintable()) && line < textArea.getLineCount() - 1) {
+ t = textArea.getTokenListForLine(++line);
+ return getNextImportantToken(t, textArea, line);
+ }
+ return t;
+ }
+
+
+ /**
+ * Provides a way to determine the next visually represented model
+ * location at which one might place a caret.
+ * Some views may not be visible,
+ * they might not be in the same order found in the model, or they just
+ * might not allow access to some of the locations in the model.+ *
+ * NOTE: You should only call this method if the passed-in
+ * javax.swing.text.View
is an instance of
+ * {@link TokenOrientedView} and javax.swing.text.TabExpander
;
+ * otherwise, a ClassCastException
could be thrown.
+ *
+ * @param pos the position to convert >= 0
+ * @param a the allocated region in which to render
+ * @param direction the direction from the current position that can
+ * be thought of as the arrow keys typically found on a keyboard.
+ * This will be one of the following values:
+ *
direction
+ * doesn't have one of the legal values above
+ */
+ public static int getNextVisualPositionFrom(int pos, Position.Bias b,
+ Shape a, int direction,
+ Position.Bias[] biasRet, View view)
+ throws BadLocationException {
+
+ RSyntaxTextArea target = (RSyntaxTextArea) view.getContainer();
+ biasRet[0] = Position.Bias.Forward;
+
+ // Do we want the "next position" above, below, to the left or right?
+ switch (direction) {
+
+ case NORTH:
+ case SOUTH:
+ if (pos == -1) {
+ pos = (direction == NORTH) ?
+ Math.max(0, view.getEndOffset() - 1) :
+ view.getStartOffset();
+ break;
+ }
+ Caret c = (target != null) ? target.getCaret() : null;
+ // YECK! Ideally, the x location from the magic caret
+ // position would be passed in.
+ Point mcp;
+ if (c != null) {
+ mcp = c.getMagicCaretPosition();
+ } else {
+ mcp = null;
+ }
+ int x;
+ if (mcp == null) {
+ Rectangle loc = Objects.requireNonNull(target).modelToView(pos);
+ x = (loc == null) ? 0 : loc.x;
+ } else {
+ x = mcp.x;
+ }
+ if (direction == NORTH) {
+ pos = getPositionAbove(target, pos, x, (TabExpander) view);
+ } else {
+ pos = getPositionBelow(target, pos, x, (TabExpander) view);
+ }
+ break;
+
+ case WEST:
+ if (pos == -1) {
+ pos = Math.max(0, view.getEndOffset() - 1);
+ } else {
+ pos = Math.max(0, pos - 1);
+ if (target.isCodeFoldingEnabled()) {
+ int last = target.getLineOfOffset(pos + 1);
+ int current = target.getLineOfOffset(pos);
+ if (last != current) { // If moving up a line...
+ FoldManager fm = target.getFoldManager();
+ if (fm.isLineHidden(current)) {
+ while (--current > 0 && fm.isLineHidden(current)) {
+ ;
+ }
+ pos = target.getLineEndOffset(current) - 1;
+ }
+ }
+ }
+ }
+ break;
+
+ case EAST:
+ if (pos == -1) {
+ pos = view.getStartOffset();
+ } else {
+ pos = Math.min(pos + 1, view.getDocument().getLength());
+ if (target.isCodeFoldingEnabled()) {
+ int last = target.getLineOfOffset(pos - 1);
+ int current = target.getLineOfOffset(pos);
+ if (last != current) { // If moving down a line...
+ FoldManager fm = target.getFoldManager();
+ if (fm.isLineHidden(current)) {
+ int lineCount = target.getLineCount();
+ while (++current < lineCount && fm.isLineHidden(current)) {
+ ;
+ }
+ pos = current == lineCount ?
+ target.getLineEndOffset(last) - 1 : // Was the last visible line
+ target.getLineStartOffset(current);
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ throw new IllegalArgumentException(
+ "Bad direction: " + direction);
+ }
+
+ return pos;
+
+ }
+
+
+ /**
+ * Determines the position in the model that is closest to the given
+ * view location in the row above. The component given must have a
+ * size to compute the result. If the component doesn't have a size
+ * a value of -1 will be returned.
+ *
+ * @param c the editor
+ * @param offs the offset in the document >= 0
+ * @param x the X coordinate >= 0
+ * @return the position >= 0 if the request can be computed, otherwise
+ * a value of -1 will be returned.
+ * @throws BadLocationException if the offset is out of range
+ */
+ public static final int getPositionAbove(RSyntaxTextArea c, int offs,
+ float x, TabExpander e) throws BadLocationException {
+
+ TokenOrientedView tov = (TokenOrientedView) e;
+ Token token = tov.getTokenListForPhysicalLineAbove(offs);
+ if (token == null) {
+ return -1;
+ }
+
+ // A line containing only Token.NULL is an empty line.
+ else if (token.getType() == Token.NULL) {
+ int line = c.getLineOfOffset(offs); // Sure to be >0 ??
+ return c.getLineStartOffset(line - 1);
+ } else {
+ return token.getListOffset(c, e, 0, x);
+ }
+
+ }
+
+
+ /**
+ * Determines the position in the model that is closest to the given
+ * view location in the row below. The component given must have a
+ * size to compute the result. If the component doesn't have a size
+ * a value of -1 will be returned.
+ *
+ * @param c the editor
+ * @param offs the offset in the document >= 0
+ * @param x the X coordinate >= 0
+ * @return the position >= 0 if the request can be computed, otherwise
+ * a value of -1 will be returned.
+ * @throws BadLocationException if the offset is out of range
+ */
+ public static final int getPositionBelow(RSyntaxTextArea c, int offs,
+ float x, TabExpander e) throws BadLocationException {
+
+ TokenOrientedView tov = (TokenOrientedView) e;
+ Token token = tov.getTokenListForPhysicalLineBelow(offs);
+ if (token == null) {
+ return -1;
+ }
+
+ // A line containing only Token.NULL is an empty line.
+ else if (token.getType() == Token.NULL) {
+ int line = c.getLineOfOffset(offs); // Sure to be > c.getLineCount()-1 ??
// return c.getLineStartOffset(line+1);
-FoldManager fm = c.getFoldManager();
-line = fm.getVisibleLineBelow(line);
-return c.getLineStartOffset(line);
- }
-
- else {
- return token.getListOffset(c, e, 0, x);
- }
-
- }
-
-
- /**
- * Returns the last non-whitespace, non-comment token, starting with the
- * specified line.
- *
- * @param textArea The text area.
- * @param line The line at which to start looking.
- * @return The last non-whitespace, non-comment token, or null
- * if there isn't one.
- * @see #getNextImportantToken(Token, RSyntaxTextArea, int)
- */
- public static final Token getPreviousImportantToken(
- RSyntaxTextArea textArea, int line){
- if (line<0) {
- return null;
- }
- Token t = textArea.getTokenListForLine(line);
- if (t!=null) {
- t = t.getLastNonCommentNonWhitespaceToken();
- if (t!=null) {
- return t;
- }
- }
- return getPreviousImportantToken(textArea, line-1);
- }
-
-
- /**
- * Returns the token at the specified index, or null
if
- * the given offset isn't in this token list's range.tokenList
- * is null; callers should check for themselves.
- *
- * @param tokenList The list of tokens in which to search.
- * @param offset The offset at which to get the token.
- * @return The token at offset
, or null
if
- * none of the tokens are at that offset.
- */
- public static final Token getTokenAtOffset(Token tokenList, int offset) {
- for (Token t=tokenList; t!=null && t.isPaintable(); t=t.getNextToken()){
- if (t.containsPosition(offset))
- return t;
- }
- return null;
- }
-
-
- /**
- * Returns the end of the word at the given offset.
- *
- * @param textArea The text area.
- * @param offs The offset into the text area's content.
- * @return The end offset of the word.
- * @throws BadLocationException If offs
is invalid.
- * @see #getWordStart(RSyntaxTextArea, int)
- */
- public static int getWordEnd(RSyntaxTextArea textArea, int offs)
- throws BadLocationException {
-
- Document doc = textArea.getDocument();
- int endOffs = textArea.getLineEndOffsetOfCurrentLine();
- int lineEnd = Math.min(endOffs, doc.getLength());
- if (offs == lineEnd) { // End of the line.
- return offs;
- }
-
- String s = doc.getText(offs, lineEnd-offs-1);
- if (s!=null && s.length()>0) { // Should always be true
- int i = 0;
- int count = s.length();
- char ch = s.charAt(i);
- if (Character.isWhitespace(ch)) {
- while (i
- *
- * This method also assumes that the passed-in token list begins at
- * x-pixel 0
in the view (for tab purposes).
- *
- * @param tokenList The tokenList list representing the text.
- * @param textArea The text area in which this token list resides.
- * @param e The tab expander. This value cannot be null
.
- * @return The width of the token list, in pixels.
- */
- public static final float getTokenListWidth(Token tokenList,
- RSyntaxTextArea textArea,
- TabExpander e) {
- return getTokenListWidth(tokenList, textArea, e, 0);
- }
-
-
- /**
- * Determines the width of the given token list taking tabs
- * into consideration. This is implemented in a 1.1 style coordinate
- * system where ints are used and 72dpi is assumed.
- *
- * @param tokenList The token list list representing the text.
- * @param textArea The text area in which this token list resides.
- * @param e The tab expander. This value cannot be
+ *
+ * This method also assumes that the passed-in token list begins at
+ * x-pixel
+ *
+ * @param tokenList The token list list representing the text.
+ * @param textArea The text area in which this token list resides.
+ * @param e The tab expander. This value cannot be null
.
- * @param x0 The x-pixel coordinate of the start of the token list.
- * @return The width of the token list, in pixels.
- * @see #getTokenListWidthUpTo
- */
- public static final float getTokenListWidth(final Token tokenList,
- RSyntaxTextArea textArea,
- TabExpander e, float x0) {
- float width = x0;
- for (Token t=tokenList; t!=null&&t.isPaintable(); t=t.getNextToken()) {
- width += t.getWidth(textArea, e, width);
- }
- return width - x0;
- }
-
-
- /**
- * Determines the width of the given token list taking tabs into
- * consideration and only up to the given index in the document
- * (exclusive).
- *
- * @param tokenList The token list representing the text.
- * @param textArea The text area in which this token list resides.
- * @param e The tab expander. This value cannot be null
.
- * @param x0 The x-pixel coordinate of the start of the token list.
- * @param upTo The document position at which you want to stop,
- * exclusive. If this position is before the starting position
- * of the token list, a width of 0
will be
- * returned; similarly, if this position comes after the entire
- * token list, the width of the entire token list is returned.
- * @return The width of the token list, in pixels, up to, but not
- * including, the character at position upTo
.
- * @see #getTokenListWidth
- */
- public static final float getTokenListWidthUpTo(final Token tokenList,
- RSyntaxTextArea textArea, TabExpander e,
- float x0, int upTo) {
- float width = 0;
- for (Token t=tokenList; t!=null&&t.isPaintable(); t=t.getNextToken()) {
- if (t.containsPosition(upTo)) {
- return width + t.getWidthUpTo(upTo-t.getOffset(), textArea, e,
- x0+width);
- }
- width += t.getWidth(textArea, e, x0+width);
- }
- return width;
- }
-
-
- /**
- * Returns whether or not this character is a "bracket" to be matched by
- * such programming languages as C, C++, and Java.
- *
- * @param ch The character to check.
- * @return Whether or not the character is a "bracket" - one of '(', ')',
- * '[', ']', '{', and '}'.
- */
- public static final boolean isBracket(char ch) {
- // We need the first condition as it might be that ch>255, and thus
- // not in our table. '}' is the highest-valued char in the bracket
- // set.
- return ch<='}' && (dataTable[ch]&BRACKET_MASK)>0;
- }
-
-
- /**
- * Returns whether or not a character is a digit (0-9).
- *
- * @param ch The character to check.
- * @return Whether or not the character is a digit.
- */
- public static final boolean isDigit(char ch) {
- // We do it this way as we'd need to do two conditions anyway (first
- // to check that ch<255 so it can index into our table, then whether
- // that table position has the digit mask).
- return ch>='0' && ch<='9';
- }
-
-
- /**
- * Returns whether or not this character is a hex character. This method
- * accepts both upper- and lower-case letters a-f.
- *
- * @param ch The character to check.
- * @return Whether or not the character is a hex character 0-9, a-f, or
- * A-F.
- */
- public static final boolean isHexCharacter(char ch) {
- // We need the first condition as it could be that ch>255 (and thus
- // not a valid index into our table). 'f' is the highest-valued
- // char that is a valid hex character.
- return (ch<='f') && (dataTable[ch]&HEX_CHARACTER_MASK)>0;
- }
-
-
- /**
- * Returns whether a character is a Java operator. Note that C and C++
- * operators are the same as Java operators.
- *
- * @param ch The character to check.
- * @return Whether or not the character is a Java operator.
- */
- public static final boolean isJavaOperator(char ch) {
- // We need the first condition as it could be that ch>255 (and thus
- // not a valid index into our table). '~' is the highest-valued
- // char that is a valid Java operator.
- return (ch<='~') && (dataTable[ch]&JAVA_OPERATOR_MASK)>0;
- }
-
-
- /**
- * Returns whether a character is a US-ASCII letter (A-Z or a-z).
- *
- * @param ch The character to check.
- * @return Whether or not the character is a US-ASCII letter.
- */
- public static final boolean isLetter(char ch) {
- // We need the first condition as it could be that ch>255 (and thus
- // not a valid index into our table).
- return (ch<='z') && (dataTable[ch]&LETTER_MASK)>0;
- }
-
-
- /**
- * Returns whether or not a character is a US-ASCII letter or a digit.
- *
- * @param ch The character to check.
- * @return Whether or not the character is a US-ASCII letter or a digit.
- */
- public static final boolean isLetterOrDigit(char ch) {
- // We need the first condition as it could be that ch>255 (and thus
- // not a valid index into our table).
- return (ch<='z') && (dataTable[ch]&LETTER_OR_DIGIT_MASK)>0;
- }
-
-
- /**
- * Returns whether the specified color is "light" to use as a foreground.
- * Colors that return true
indicate that the current Look and
- * Feel probably uses light text colors on a dark background.
- *
- * @param fg The foreground color.
- * @return Whether it is a "light" foreground color.
- * @see #getHyperlinkForeground()
- */
- public static final boolean isLightForeground(Color fg) {
- return fg.getRed()>0xa0 && fg.getGreen()>0xa0 && fg.getBlue()>0xa0;
- }
-
-
- /**
- * Returns whether or not a character is a whitespace character (either
- * a space ' ' or tab '\t'). This checks for the Unicode character values
- * 0x0020 and 0x0009.
- *
- * @param ch The character to check.
- * @return Whether or not the character is a whitespace character.
- */
- public static final boolean isWhitespace(char ch) {
- // We do it this way as we'd need to do two conditions anyway (first
- // to check that ch<255 so it can index into our table, then whether
- // that table position has the whitespace mask).
- return ch==' ' || ch=='\t';
- }
-
-
- /**
- * Returns whether a regular expression token can follow the specified
- * token in JavaScript.
- *
- * @param t The token to check, which may be null
.
- * @return Whether a regular expression token may follow this one in
- * JavaScript.
- */
- public static boolean regexCanFollowInJavaScript(Token t) {
- char ch;
- // We basically try to mimic Eclipse's JS editor's behavior here.
- return t==null ||
- //t.isOperator() ||
- (t.length()==1 && (
- (ch=t.charAt(0))=='=' ||
- ch=='(' ||
- ch==',' ||
- ch=='?' ||
- ch==':' ||
- ch=='[' ||
- ch=='!' ||
- ch=='&'
- )) ||
- /* Operators "==", "===", "!=", "!==" */
- (t.getType()==Token.OPERATOR &&
- t.charAt(t.length()-1)=='=') ||
- t.is(Token.RESERVED_WORD_2, JS_KEYWORD_RETURN);
- }
-
-
- /**
- * If the character is an upper-case US-ASCII letter, it returns the
- * lower-case version of that letter; otherwise, it just returns the
- * character.
- *
- * @param ch The character to lower-case (if it is a US-ASCII upper-case
- * character).
- * @return The lower-case version of the character.
- */
- public static final char toLowerCase(char ch) {
- // We can logical OR with 32 because A-Z are 65-90 in the ASCII table
- // and none of them have the 6th bit (32) set, and a-z are 97-122 in
- // the ASCII table, which is 32 over from A-Z.
- // We do it this way as we'd need to do two conditions anyway (first
- // to check that ch<255 so it can index into our table, then whether
- // that table position has the upper-case mask).
- if (ch>='A' && ch<='Z')
- return (char)(ch | 0x20);
- return ch;
- }
-
-
- /**
- * Returns an integer constant representing the OS. This can be handy for
- * special case situations such as Mac OS-X (special application
- * registration) or Windows (allow mixed case, etc.).
- *
- * @return An integer constant representing the OS.
- */
- public static final int getOS() {
- return OS;
- }
-
-
- /**
- * Returns an integer constant representing the OS. This can be handy for
- * special case situations such as Mac OS-X (special application
- * registration) or Windows (allow mixed case, etc.).
- *
- * @return An integer constant representing the OS.
- */
- private static final int getOSImpl() {
- int os = OS_OTHER;
- String osName = System.getProperty("os.name");
- if (osName!=null) { // Should always be true.
- osName = osName.toLowerCase();
- if (osName.indexOf("windows") > -1)
- os = OS_WINDOWS;
- else if (osName.indexOf("mac os x") > -1)
- os = OS_MAC_OSX;
- else if (osName.indexOf("linux") > -1)
- os = OS_LINUX;
- else
- os = OS_OTHER;
- }
- return os;
- }
-
-
- /**
- * Creates a regular expression pattern that matches a "wildcard" pattern.
- *
- * @param wildcard The wildcard pattern.
- * @param matchCase Whether the pattern should be case sensitive.
- * @param escapeStartChar Whether to escape a starting '^'
- * character.
- * @return The pattern.
- */
- public static Pattern wildcardToPattern(String wildcard, boolean matchCase,
- boolean escapeStartChar) {
-
- int flags = 0;
- if (!matchCase) {
- flags = Pattern.CASE_INSENSITIVE|Pattern.UNICODE_CASE;
- }
-
- StringBuilder sb = new StringBuilder();
- for (int i=0; inull
+ * if there isn't one.
+ * @see #getNextImportantToken(Token, RSyntaxTextArea, int)
+ */
+ public static final Token getPreviousImportantToken(
+ RSyntaxTextArea textArea, int line) {
+ if (line < 0) {
+ return null;
+ }
+ Token t = textArea.getTokenListForLine(line);
+ if (t != null) {
+ t = t.getLastNonCommentNonWhitespaceToken();
+ if (t != null) {
+ return t;
+ }
+ }
+ return getPreviousImportantToken(textArea, line - 1);
+ }
+
+
+ /**
+ * Returns the token at the specified index, or null
if
+ * the given offset isn't in this token list's range.
+ * Note that this method does NOT check to see if tokenList
+ * is null; callers should check for themselves.
+ *
+ * @param tokenList The list of tokens in which to search.
+ * @param offset The offset at which to get the token.
+ * @return The token at offset
, or null
if
+ * none of the tokens are at that offset.
+ */
+ public static final Token getTokenAtOffset(Token tokenList, int offset) {
+ for (Token t = tokenList; t != null && t.isPaintable(); t = t.getNextToken()) {
+ if (t.containsPosition(offset)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Returns the end of the word at the given offset.
+ *
+ * @param textArea The text area.
+ * @param offs The offset into the text area's content.
+ * @return The end offset of the word.
+ * @throws BadLocationException If offs
is invalid.
+ * @see #getWordStart(RSyntaxTextArea, int)
+ */
+ public static int getWordEnd(RSyntaxTextArea textArea, int offs)
+ throws BadLocationException {
+
+ Document doc = textArea.getDocument();
+ int endOffs = textArea.getLineEndOffsetOfCurrentLine();
+ int lineEnd = Math.min(endOffs, doc.getLength());
+ if (offs == lineEnd) { // End of the line.
+ return offs;
+ }
+
+ String s = doc.getText(offs, lineEnd - offs - 1);
+ if (s != null && s.length() > 0) { // Should always be true
+ int i = 0;
+ int count = s.length();
+ char ch = s.charAt(i);
+ if (Character.isWhitespace(ch)) {
+ while (i < count && Character.isWhitespace(s.charAt(i++))) {
+ ;
+ }
+ } else if (Character.isLetterOrDigit(ch)) {
+ while (i < count && Character.isLetterOrDigit(s.charAt(i++))) {
+ ;
+ }
+ } else {
+ i = 2;
+ }
+ offs += i - 1;
+ }
+
+ return offs;
+
+ }
+
+ /**
+ * Returns the start of the word at the given offset.
+ *
+ * @param textArea The text area.
+ * @param offs The offset into the text area's content.
+ * @return The start offset of the word.
+ * @throws BadLocationException If offs
is invalid.
+ * @see #getWordEnd(RSyntaxTextArea, int)
+ */
+ public static int getWordStart(RSyntaxTextArea textArea, int offs)
+ throws BadLocationException {
+
+ Document doc = textArea.getDocument();
+ Element line = getLineElem(doc, offs);
+ if (line == null) {
+ throw new BadLocationException("No word at " + offs, offs);
+ }
+
+ int lineStart = line.getStartOffset();
+ if (offs == lineStart) { // Start of the line.
+ return offs;
+ }
+
+ int endOffs = Math.min(offs + 1, doc.getLength());
+ String s = doc.getText(lineStart, endOffs - lineStart);
+ if (s != null && s.length() > 0) {
+ int i = s.length() - 1;
+ char ch = s.charAt(i);
+ if (Character.isWhitespace(ch)) {
+ while (i > 0 && Character.isWhitespace(s.charAt(i - 1))) {
+ i--;
+ }
+ offs = lineStart + i;
+ } else if (Character.isLetterOrDigit(ch)) {
+ while (i > 0 && Character.isLetterOrDigit(s.charAt(i - 1))) {
+ i--;
+ }
+ offs = lineStart + i;
+ }
+
+ }
+
+ return offs;
+
+ }
+
+
+ /**
+ * Determines the width of the given token list taking tabs
+ * into consideration. This is implemented in a 1.1 style coordinate
+ * system where ints are used and 72dpi is assumed.0
in the view (for tab purposes).
+ *
+ * @param tokenList The tokenList list representing the text.
+ * @param textArea The text area in which this token list resides.
+ * @param e The tab expander. This value cannot be null
.
+ * @return The width of the token list, in pixels.
+ */
+ public static final float getTokenListWidth(Token tokenList,
+ RSyntaxTextArea textArea,
+ TabExpander e) {
+ return getTokenListWidth(tokenList, textArea, e, 0);
+ }
+
+
+ /**
+ * Determines the width of the given token list taking tabs
+ * into consideration. This is implemented in a 1.1 style coordinate
+ * system where ints are used and 72dpi is assumed.null
.
+ * @param x0 The x-pixel coordinate of the start of the token list.
+ * @return The width of the token list, in pixels.
+ * @see #getTokenListWidthUpTo
+ */
+ public static final float getTokenListWidth(final Token tokenList,
+ RSyntaxTextArea textArea,
+ TabExpander e, float x0) {
+ float width = x0;
+ for (Token t = tokenList; t != null && t.isPaintable(); t = t.getNextToken()) {
+ width += t.getWidth(textArea, e, width);
+ }
+ return width - x0;
+ }
+
+
+ /**
+ * Determines the width of the given token list taking tabs into
+ * consideration and only up to the given index in the document
+ * (exclusive).
+ *
+ * @param tokenList The token list representing the text.
+ * @param textArea The text area in which this token list resides.
+ * @param e The tab expander. This value cannot be null
.
+ * @param x0 The x-pixel coordinate of the start of the token list.
+ * @param upTo The document position at which you want to stop,
+ * exclusive. If this position is before the starting position
+ * of the token list, a width of 0
will be
+ * returned; similarly, if this position comes after the entire
+ * token list, the width of the entire token list is returned.
+ * @return The width of the token list, in pixels, up to, but not
+ * including, the character at position upTo
.
+ * @see #getTokenListWidth
+ */
+ public static final float getTokenListWidthUpTo(final Token tokenList,
+ RSyntaxTextArea textArea, TabExpander e,
+ float x0, int upTo) {
+ float width = 0;
+ for (Token t = tokenList; t != null && t.isPaintable(); t = t.getNextToken()) {
+ if (t.containsPosition(upTo)) {
+ return width + t.getWidthUpTo(upTo - t.getOffset(), textArea, e,
+ x0 + width);
+ }
+ width += t.getWidth(textArea, e, x0 + width);
+ }
+ return width;
+ }
+
+
+ /**
+ * Returns whether or not this character is a "bracket" to be matched by
+ * such programming languages as C, C++, and Java.
+ *
+ * @param ch The character to check.
+ * @return Whether or not the character is a "bracket" - one of '(', ')',
+ * '[', ']', '{', and '}'.
+ */
+ public static final boolean isBracket(char ch) {
+ // We need the first condition as it might be that ch>255, and thus
+ // not in our table. '}' is the highest-valued char in the bracket
+ // set.
+ return ch <= '}' && (dataTable[ch] & BRACKET_MASK) > 0;
+ }
+
+
+ /**
+ * Returns whether or not a character is a digit (0-9).
+ *
+ * @param ch The character to check.
+ * @return Whether or not the character is a digit.
+ */
+ public static final boolean isDigit(char ch) {
+ // We do it this way as we'd need to do two conditions anyway (first
+ // to check that ch<255 so it can index into our table, then whether
+ // that table position has the digit mask).
+ return ch >= '0' && ch <= '9';
+ }
+
+
+ /**
+ * Returns whether or not this character is a hex character. This method
+ * accepts both upper- and lower-case letters a-f.
+ *
+ * @param ch The character to check.
+ * @return Whether or not the character is a hex character 0-9, a-f, or
+ * A-F.
+ */
+ public static final boolean isHexCharacter(char ch) {
+ // We need the first condition as it could be that ch>255 (and thus
+ // not a valid index into our table). 'f' is the highest-valued
+ // char that is a valid hex character.
+ return (ch <= 'f') && (dataTable[ch] & HEX_CHARACTER_MASK) > 0;
+ }
+
+
+ /**
+ * Returns whether a character is a Java operator. Note that C and C++
+ * operators are the same as Java operators.
+ *
+ * @param ch The character to check.
+ * @return Whether or not the character is a Java operator.
+ */
+ public static final boolean isJavaOperator(char ch) {
+ // We need the first condition as it could be that ch>255 (and thus
+ // not a valid index into our table). '~' is the highest-valued
+ // char that is a valid Java operator.
+ return (ch <= '~') && (dataTable[ch] & JAVA_OPERATOR_MASK) > 0;
+ }
+
+
+ /**
+ * Returns whether a character is a US-ASCII letter (A-Z or a-z).
+ *
+ * @param ch The character to check.
+ * @return Whether or not the character is a US-ASCII letter.
+ */
+ public static final boolean isLetter(char ch) {
+ // We need the first condition as it could be that ch>255 (and thus
+ // not a valid index into our table).
+ return (ch <= 'z') && (dataTable[ch] & LETTER_MASK) > 0;
+ }
+
+
+ /**
+ * Returns whether or not a character is a US-ASCII letter or a digit.
+ *
+ * @param ch The character to check.
+ * @return Whether or not the character is a US-ASCII letter or a digit.
+ */
+ public static final boolean isLetterOrDigit(char ch) {
+ // We need the first condition as it could be that ch>255 (and thus
+ // not a valid index into our table).
+ return (ch <= 'z') && (dataTable[ch] & LETTER_OR_DIGIT_MASK) > 0;
+ }
+
+
+ /**
+ * Returns whether the specified color is "light" to use as a foreground.
+ * Colors that return true
indicate that the current Look and
+ * Feel probably uses light text colors on a dark background.
+ *
+ * @param fg The foreground color.
+ * @return Whether it is a "light" foreground color.
+ * @see #getHyperlinkForeground()
+ */
+ public static final boolean isLightForeground(Color fg) {
+ return fg.getRed() > 0xa0 && fg.getGreen() > 0xa0 && fg.getBlue() > 0xa0;
+ }
+
+
+ /**
+ * Returns whether or not a character is a whitespace character (either
+ * a space ' ' or tab '\t'). This checks for the Unicode character values
+ * 0x0020 and 0x0009.
+ *
+ * @param ch The character to check.
+ * @return Whether or not the character is a whitespace character.
+ */
+ public static final boolean isWhitespace(char ch) {
+ // We do it this way as we'd need to do two conditions anyway (first
+ // to check that ch<255 so it can index into our table, then whether
+ // that table position has the whitespace mask).
+ return ch == ' ' || ch == '\t';
+ }
+
+
+ /**
+ * Returns whether a regular expression token can follow the specified
+ * token in JavaScript.
+ *
+ * @param t The token to check, which may be null
.
+ * @return Whether a regular expression token may follow this one in
+ * JavaScript.
+ */
+ public static boolean regexCanFollowInJavaScript(Token t) {
+ char ch;
+ // We basically try to mimic Eclipse's JS editor's behavior here.
+ return t == null ||
+ //t.isOperator() ||
+ (t.length() == 1 && (
+ (ch = t.charAt(0)) == '=' ||
+ ch == '(' ||
+ ch == ',' ||
+ ch == '?' ||
+ ch == ':' ||
+ ch == '[' ||
+ ch == '!' ||
+ ch == '&'
+ )) ||
+ /* Operators "==", "===", "!=", "!==" */
+ (t.getType() == Token.OPERATOR &&
+ t.charAt(t.length() - 1) == '=') ||
+ t.is(Token.RESERVED_WORD_2, JS_KEYWORD_RETURN);
+ }
+
+
+ /**
+ * If the character is an upper-case US-ASCII letter, it returns the
+ * lower-case version of that letter; otherwise, it just returns the
+ * character.
+ *
+ * @param ch The character to lower-case (if it is a US-ASCII upper-case
+ * character).
+ * @return The lower-case version of the character.
+ */
+ public static final char toLowerCase(char ch) {
+ // We can logical OR with 32 because A-Z are 65-90 in the ASCII table
+ // and none of them have the 6th bit (32) set, and a-z are 97-122 in
+ // the ASCII table, which is 32 over from A-Z.
+ // We do it this way as we'd need to do two conditions anyway (first
+ // to check that ch<255 so it can index into our table, then whether
+ // that table position has the upper-case mask).
+ if (ch >= 'A' && ch <= 'Z') {
+ return (char) (ch | 0x20);
+ }
+ return ch;
+ }
+
+
+ /**
+ * Returns an integer constant representing the OS. This can be handy for
+ * special case situations such as Mac OS-X (special application
+ * registration) or Windows (allow mixed case, etc.).
+ *
+ * @return An integer constant representing the OS.
+ */
+ public static final int getOS() {
+ return OS;
+ }
+
+
+ /**
+ * Returns an integer constant representing the OS. This can be handy for
+ * special case situations such as Mac OS-X (special application
+ * registration) or Windows (allow mixed case, etc.).
+ *
+ * @return An integer constant representing the OS.
+ */
+ private static final int getOSImpl() {
+ int os = OS_OTHER;
+ String osName = System.getProperty("os.name");
+ if (osName != null) { // Should always be true.
+ osName = osName.toLowerCase();
+ if (osName.contains("windows")) {
+ os = OS_WINDOWS;
+ } else if (osName.contains("mac os x")) {
+ os = OS_MAC_OSX;
+ } else if (osName.contains("linux")) {
+ os = OS_LINUX;
+ } else {
+ os = OS_OTHER;
+ }
+ }
+ return os;
+ }
+
+
+ /**
+ * Creates a regular expression pattern that matches a "wildcard" pattern.
+ *
+ * @param wildcard The wildcard pattern.
+ * @param matchCase Whether the pattern should be case sensitive.
+ * @param escapeStartChar Whether to escape a starting '^'
+ * character.
+ * @return The pattern.
+ */
+ public static Pattern wildcardToPattern(String wildcard, boolean matchCase,
+ boolean escapeStartChar) {
+
+ int flags = 0;
+ if (!matchCase) {
+ flags = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < wildcard.length(); i++) {
+ char ch = wildcard.charAt(i);
+ switch (ch) {
+ case '*':
+ sb.append(".*");
+ break;
+ case '?':
+ sb.append('.');
+ break;
+ case '^':
+ if (i > 0 || escapeStartChar) {
+ sb.append('\\');
+ }
+ sb.append('^');
+ break;
+ case '\\':
+ case '.':
+ case '|':
+ case '+':
+ case '-':
+ case '$':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ case '(':
+ case ')':
+ sb.append('\\').append(ch);
+ break;
+ default:
+ sb.append(ch);
+ break;
+ }
+ }
+
+ Pattern p = null;
+ try {
+ p = Pattern.compile(sb.toString(), flags);
+ } catch (PatternSyntaxException pse) {
+ pse.printStackTrace();
+ p = Pattern.compile(".+");
+ }
+
+ return p;
+
+ }
}
\ No newline at end of file
diff --git a/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rtextarea/RTextAreaEditorKit.java b/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rtextarea/RTextAreaEditorKit.java
index f95aa1260..277064c2f 100644
--- a/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rtextarea/RTextAreaEditorKit.java
+++ b/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rtextarea/RTextAreaEditorKit.java
@@ -902,6 +902,8 @@ public class RTextAreaEditorKit extends DefaultEditorKit {
}
} catch (BadLocationException bl) {
}
+ }else if (textArea == null) {
+ throw new IllegalArgumentException("RTextArea can not be null!");
}
if (beep)
diff --git a/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rtextarea/VolatileImageBackgroundPainterStrategy.java b/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rtextarea/VolatileImageBackgroundPainterStrategy.java
index 11aaca364..c7be420de 100644
--- a/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rtextarea/VolatileImageBackgroundPainterStrategy.java
+++ b/designer-base/src/main/java/com/fr/design/gui/syntax/ui/rtextarea/VolatileImageBackgroundPainterStrategy.java
@@ -9,6 +9,8 @@
*/
package com.fr.design.gui.syntax.ui.rtextarea;
+import com.fr.log.FineLoggerFactory;
+
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.VolatileImage;
@@ -95,9 +97,9 @@ public class VolatileImageBackgroundPainterStrategy
try {
tracker.waitForID(1);
} catch (InterruptedException e) {
- e.printStackTrace();
+ FineLoggerFactory.getLogger().error(e.getMessage(),e);
bgImage = null;
- return;
+ Thread.currentThread().interrupt();
} finally {
tracker.removeImage(i, 1);
}
@@ -106,9 +108,9 @@ public class VolatileImageBackgroundPainterStrategy
try {
tracker.waitForID(0);
} catch (InterruptedException e) {
- e.printStackTrace();
+ FineLoggerFactory.getLogger().error(e.getMessage(),e);
bgImage = null;
- return;
+ Thread.currentThread().interrupt();
} finally {
tracker.removeImage(bgImage, 0);
}
diff --git a/designer-base/src/main/java/com/fr/design/icon/LockIcon.java b/designer-base/src/main/java/com/fr/design/icon/LockIcon.java
index 934d53b08..ee54b6ea8 100644
--- a/designer-base/src/main/java/com/fr/design/icon/LockIcon.java
+++ b/designer-base/src/main/java/com/fr/design/icon/LockIcon.java
@@ -34,6 +34,7 @@ public class LockIcon extends ImageIcon {
}
}
+ @Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (mainImage != null) {
g.drawImage(mainImage, x, y, c);
@@ -48,6 +49,7 @@ public class LockIcon extends ImageIcon {
*
* @param image the image
*/
+ @Override
protected void loadImage(Image image) {
synchronized (tracker) {
tracker.addImage(image, 0);
@@ -55,6 +57,7 @@ public class LockIcon extends ImageIcon {
tracker.waitForID(0, 0);
} catch (InterruptedException e) {
FineLoggerFactory.getLogger().error(e.getMessage(), e);
+ Thread.currentThread().interrupt();
}
loadStatus = tracker.statusID(0, false);
@@ -70,6 +73,7 @@ public class LockIcon extends ImageIcon {
*
* @return the Image
object for this ImageIcon
*/
+ @Override
public Image getImage() {
return mainImage;
}
@@ -85,6 +89,7 @@ public class LockIcon extends ImageIcon {
*
* @return the width in pixels of this icon
*/
+ @Override
public int getIconWidth() {
return width;
}
@@ -94,6 +99,7 @@ public class LockIcon extends ImageIcon {
*
* @return the height in pixels of this icon
*/
+ @Override
public int getIconHeight() {
return height;
}
diff --git a/designer-base/src/main/java/com/fr/design/icon/WarningIcon.java b/designer-base/src/main/java/com/fr/design/icon/WarningIcon.java
index 9b22b4035..d70d62225 100644
--- a/designer-base/src/main/java/com/fr/design/icon/WarningIcon.java
+++ b/designer-base/src/main/java/com/fr/design/icon/WarningIcon.java
@@ -59,6 +59,7 @@ public class WarningIcon extends ImageIcon {
tracker.waitForID(0, 0);
} catch (InterruptedException e) {
FineLoggerFactory.getLogger().error(e.getMessage(), e);
+ Thread.currentThread().interrupt();
}
tracker.statusID(0, false);
diff --git a/designer-base/src/main/java/com/fr/design/report/WatermarkPane.java b/designer-base/src/main/java/com/fr/design/report/WatermarkPane.java
index e190d4038..40fe942d2 100644
--- a/designer-base/src/main/java/com/fr/design/report/WatermarkPane.java
+++ b/designer-base/src/main/java/com/fr/design/report/WatermarkPane.java
@@ -7,6 +7,7 @@ import com.fr.design.gui.icombobox.UIComboBox;
import com.fr.design.gui.icontainer.UIScrollPane;
import com.fr.design.gui.ilable.UILabel;
import com.fr.design.gui.ispinner.UISpinner;
+import com.fr.design.gui.ispinner.UnsignedIntUISpinner;
import com.fr.design.gui.style.FRFontPane;
import com.fr.design.layout.FRGUIPaneFactory;
import com.fr.design.layout.TableLayout;
@@ -112,8 +113,8 @@ public class WatermarkPane extends BasicPane {
formulaPane = new TinyFormulaPane();
fontSizeComboBox = new UIComboBox(FRFontPane.FONT_SIZES);
fontSizeComboBox.setEditable(true);
- horizontalGapSpinner = new UISpinner(0, Integer.MAX_VALUE, 1, 200);
- verticalGapSpinner = new UISpinner(0, Integer.MAX_VALUE, 1, 100);
+ horizontalGapSpinner = new UnsignedIntUISpinner(0, Integer.MAX_VALUE, 1, 200);
+ verticalGapSpinner = new UnsignedIntUISpinner(0, Integer.MAX_VALUE, 1, 100);
horizontalGapSpinner.setPreferredSize(SPINNER_DIMENSION);
verticalGapSpinner.setPreferredSize(SPINNER_DIMENSION);
JPanel fontSizeTypePane = new JPanel(new BorderLayout(10,0));
diff --git a/designer-base/src/main/java/com/fr/design/utils/DrawRoutines.java b/designer-base/src/main/java/com/fr/design/utils/DrawRoutines.java
index d6b3fbb03..0fb893015 100644
--- a/designer-base/src/main/java/com/fr/design/utils/DrawRoutines.java
+++ b/designer-base/src/main/java/com/fr/design/utils/DrawRoutines.java
@@ -206,6 +206,7 @@ public class DrawRoutines {
grabber.grabPixels();
} catch (InterruptedException e) {
FineLoggerFactory.getLogger().error("PixelGrabber interrupted waiting for pixels");
+ Thread.currentThread().interrupt();
}
if ((grabber.getStatus() & ImageObserver.ABORT) != 0) {
diff --git a/designer-base/src/main/java/com/fr/env/RemoteEnvPane.java b/designer-base/src/main/java/com/fr/env/RemoteEnvPane.java
index 9b97c4359..d273a00a2 100644
--- a/designer-base/src/main/java/com/fr/env/RemoteEnvPane.java
+++ b/designer-base/src/main/java/com/fr/env/RemoteEnvPane.java
@@ -548,6 +548,7 @@ public class RemoteEnvPane extends BasicBeanPane