All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.fx.ui.controls.styledtext.StyledTextArea Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/*******************************************************************************
 * Copyright (c) 2013 IBM & BestSolution.at and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * 	Tom Schindl - initial API and implementation
 * 	IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.fx.ui.controls.styledtext;

import java.lang.ref.WeakReference;
import java.util.Collections;

import org.eclipse.fx.ui.controls.styledtext.StyledTextContent.TextChangeListener;
import org.eclipse.fx.ui.controls.styledtext.skin.StyledTextSkin;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.scene.paint.Color;
import javafx.util.Callback;

/**
 * Control which allows to implemented a code-editor
 *
 * 

* This is an experimental component provided as a preview we'll improve and * fix problems in up coming releases *

* * @noreference */ public class StyledTextArea extends Control { class ContentProperty extends SimpleObjectProperty<@NonNull StyledTextContent> { WeakReference oldContent; public ContentProperty(Object bean, String name, @NonNull StyledTextContent initialValue) { super(bean, name, initialValue); invalidated(); } @Override protected void invalidated() { if (this.oldContent != null) { StyledTextContent content = this.oldContent.get(); if (content != null) { content.removeTextChangeListener(StyledTextArea.this.textChangeListener); } } StyledTextContent newContent = get(); this.oldContent = new WeakReference(newContent); newContent.addTextChangeListener(StyledTextArea.this.textChangeListener); } } @NonNull final ObjectProperty<@NonNull StyledTextContent> contentProperty; TextChangeListener textChangeListener = new TextChangeListener() { @Override public void textChanging(TextChangingEvent event) { handleTextChanging(event); } @Override public void textChanged(TextChangedEvent event) { handleTextChanged(event); } @Override public void textSet(TextChangedEvent event) { handleTextSet(event); } }; @NonNull private final StyledTextRenderer renderer = new StyledTextRenderer(); @NonNull private final IntegerProperty caretOffsetProperty = new SimpleIntegerProperty(this, "caretOffset", -1); //$NON-NLS-1$ @NonNull private final BooleanProperty lineRulerVisible = new SimpleBooleanProperty(this, "lineRulerVisible"); //$NON-NLS-1$ @NonNull private final ObjectProperty currentSelection = new SimpleObjectProperty<>(this, "currentSelection"); //$NON-NLS-1$ @NonNull private final ObjectProperty<@NonNull Callback<@NonNull StyledTextLine, @Nullable Node>> lineRulerGraphicNodeFactory = new SimpleObjectProperty<>(this, "lineRulerGraphicNodeFactory", e -> null); //$NON-NLS-1$ /** * Represents a line shown in the control */ public interface StyledTextLine { /** * @return the plain text value */ public String getText(); /** * @return the index of the line */ public int getLineIndex(); } private int anchor; private int lastTextChangeStart; // private int lastTextChangeNewLineCount; private int lastTextChangeNewCharCount; // private int lastTextChangeReplaceLineCount; private int lastTextChangeReplaceCharCount; /** * Create a new control */ public StyledTextArea() { this.getStyleClass().add("styled-text-area"); //$NON-NLS-1$ this.contentProperty = new ContentProperty(this, "content", new DefaultContent()); //$NON-NLS-1$ setFocusTraversable(true); } @Override public String getUserAgentStylesheet() { return getClass().getResource("styledtextarea.css").toExternalForm(); //$NON-NLS-1$ } void handleTextChanging(TextChangingEvent event) { if (event.replaceCharCount < 0) { event.offset += event.replaceCharCount; event.replaceCharCount *= -1; } this.lastTextChangeStart = event.offset; // this.lastTextChangeNewLineCount = event.newLineCount; this.lastTextChangeNewCharCount = event.newCharCount; // this.lastTextChangeReplaceLineCount = event.replaceLineCount; this.lastTextChangeReplaceCharCount = event.replaceCharCount; this.renderer.textChanging(event); // Update the caret offset if it is greater than the length of the // content. // This is necessary since style range API may be called between the // handleTextChanging and handleTextChanged events and this API sets the // caretOffset. int newEndOfText = getContent().getCharCount() - event.replaceCharCount + event.newCharCount; if (getCaretOffset() > newEndOfText) setCaretOffset(newEndOfText/* , SWT.DEFAULT */); } void handleTextSet(TextChangedEvent event) { int newCharCount = getCharCount(); if( this.caretOffsetProperty.get() > newCharCount ) { this.caretOffsetProperty.set(newCharCount); } // in SWT this is done in reset() clearSelection(); if( getSkin() instanceof StyledTextSkin ) { ((StyledTextSkin)getSkin()).recalculateItems(); } } void handleTextChanged(TextChangedEvent event) { // int firstLine = getContent().getLineAtOffset(lastTextChangeStart); if (getSkin() instanceof StyledTextSkin) { ((StyledTextSkin) getSkin()).recalculateItems(); } updateSelection(this.lastTextChangeStart, this.lastTextChangeReplaceCharCount, this.lastTextChangeNewCharCount); // lastCharCount += lastTextChangeNewCharCount; // lastCharCount -= lastTextChangeReplaceCharCount; } void updateSelection(int startOffset, int replacedLength, int newLength) { if (getSelection().offset + getSelection().length > startOffset && getSelection().offset < startOffset + replacedLength) { // selection intersects replaced text. set caret behind text change setSelection(new TextSelection(startOffset + newLength, 0)/*, true, false*/); } else { // move selection to keep same text selected setSelection(new TextSelection(getSelection().offset + newLength - replacedLength, getSelection().length)/*, true, false*/); if( getSelection().length > 0 ) { int delta = this.lastTextChangeNewCharCount - this.lastTextChangeReplaceCharCount; this.caretOffsetProperty.set(Math.max(0,Math.min(getCharCount()-1,getCaretOffset() + delta))); } } } @Override protected Skin createDefaultSkin() { return new StyledTextSkin(this); } /** * Show a line ruler on the left *

* Default is false *

* * @return the property */ public @NonNull BooleanProperty lineRulerVisibleProperty() { return this.lineRulerVisible; } /** * Show/hide the line ruler *

* Default is false *

* * @param lineRulerVisible * the new state */ public void setLineRulerVisible(boolean lineRulerVisible) { lineRulerVisibleProperty().set(lineRulerVisible); } /** * Is line ruler shown *

* Default is false *

* * @return the current state */ public boolean isLineRulerVisible() { return lineRulerVisibleProperty().get(); } /** * The current caret offset * * @return the property */ public @NonNull IntegerProperty caretOffsetProperty() { return this.caretOffsetProperty; } /** * Set the current caret offset * * @param offset * the new offset */ public void setCaretOffset(int offset) { this.anchor = offset; caretOffsetProperty().set(offset); clearSelection(); } /** * Setting the caret offset and updateing the selection if requested * * @param offset * the offset * @param selection * true to update the current selection * @deprecated internal API for now */ @Deprecated public void impl_setCaretOffset(int offset, boolean selection) { if (selection) { caretOffsetProperty().set(offset); if (offset > this.anchor) { setSelectionRange(this.anchor, offset - this.anchor); } else { setSelectionRange(offset, this.anchor - offset); } } else { setCaretOffset(offset); } } /** * @return the current caret offset */ public int getCaretOffset() { return caretOffsetProperty().get(); } /** * Set the content * * @param content * the content */ public void setContent(@NonNull StyledTextContent content) { contentProperty().set(content); } /** * Access the content * * @return the content */ public @NonNull StyledTextContent getContent() { return contentProperty().get(); } /** * The content property * * @return the property */ public @NonNull ObjectProperty<@NonNull StyledTextContent> contentProperty() { return this.contentProperty; } /** * Set the style range * * @param range * the range */ public void setStyleRange(@Nullable StyleRange range) { if (range != null) { if (range.isUnstyled()) { setStyleRanges(range.start, range.length, null, null, false); } else { setStyleRanges(range.start, 0, null, new StyleRange[] { range }, false); } } else { setStyleRanges(0, 0, null, null, true); } } /** * Set the style range * * @param start * the start * @param length * the length * @param ranges * the ranges * @param styles * the style ranges */ public void setStyleRanges(int start, int length, int[] ranges, @Nullable StyleRange[] styles) { if (ranges == null || styles == null) { setStyleRanges(start, length, null, null, false); } else { setStyleRanges(start, length, ranges, styles, false); } } /** * Set the style ranges * * @param ranges * the ranges * @param styles * the style ranges */ public void setStyleRanges(int[] ranges, @Nullable StyleRange[] styles) { if (ranges == null || styles == null) { setStyleRanges(0, 0, null, null, true); } else { setStyleRanges(0, 0, ranges, styles, true); } } /** * Set the style ranges * * @param ranges * the ranges */ public void setStyleRanges(@Nullable StyleRange... ranges) { setStyleRanges(0, 0, null, ranges, true); } /** * Replace style ranges * * @param start * the start * @param length * the length * @param ranges * the new ranges */ public void replaceStyleRanges(int start, int length, @Nullable StyleRange[] ranges) { if (ranges == null) throw new IllegalArgumentException(); setStyleRanges(start, length, null, ranges, false); } void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, boolean reset) { int charCount = getContent().getCharCount(); int end = start + length; if (start > end || start < 0) { throw new IllegalArgumentException(); } if (styles != null) { if (end > charCount) { throw new IllegalArgumentException(); } if (ranges != null) { if (ranges.length != styles.length << 1) throw new IllegalArgumentException(); } int lastOffset = 0; // boolean variableHeight = false; for (int i = 0; i < styles.length; i++) { if (styles[i] == null) { throw new IllegalArgumentException(); } int rangeStart, rangeLength; if (ranges != null) { rangeStart = ranges[i << 1]; rangeLength = ranges[(i << 1) + 1]; } else { rangeStart = styles[i].start; rangeLength = styles[i].length; } if (rangeLength < 0) { throw new IllegalArgumentException(); } if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) { throw new IllegalArgumentException(); } if (lastOffset > rangeStart) { throw new IllegalArgumentException(); } // variableHeight |= styles[i].isVariableHeight(); lastOffset = rangeStart + rangeLength; } // if (variableHeight) setVariableLineHeight(); } @SuppressWarnings("unused") int rangeStart = start, rangeEnd = end; if (styles != null && styles.length > 0) { if (ranges != null) { rangeStart = ranges[0]; rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1]; } else { rangeStart = styles[0].start; rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length; } } if (reset) { this.renderer.setStyleRanges(null, null); } else { this.renderer.updateRanges(start, length, length); } if (styles != null && styles.length > 0) { this.renderer.setStyleRanges(ranges, styles); } if (getSkin() instanceof StyledTextSkin) { ((StyledTextSkin) getSkin()).recalculateItems(); } } /** * Access style ranges in the specified segment * * @param start * the start * @param length * the length * @param includeRanges * include ranges * @return the ranges */ public StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) { StyleRange[] ranges = this.renderer.getStyleRanges(start, length, includeRanges); if (ranges != null) return ranges; return new StyleRange[0]; } /** * The style range at the given offset * * @param offset * the offset * @return the style range */ public StyleRange getStyleRangeAtOffset(int offset) { if (offset < 0 || offset >= getCharCount()) { throw new IllegalArgumentException(); } // if (!isListening(ST.LineGetStyle)) { StyleRange[] ranges = this.renderer.getStyleRanges(offset, 1, true); if (ranges != null) return ranges[0]; // } return null; } /** * @return the current char count */ public int getCharCount() { return getContent().getCharCount(); } static class LineInfo { int flags; Color background; int alignment; int indent; int wrapIndent; boolean justify; int[] segments; char[] segmentsChars; int[] tabStops; public LineInfo() { } public LineInfo(LineInfo info) { if (info != null) { this.flags = info.flags; this.background = info.background; this.alignment = info.alignment; this.indent = info.indent; this.wrapIndent = info.wrapIndent; this.justify = info.justify; this.segments = info.segments; this.segmentsChars = info.segmentsChars; this.tabStops = info.tabStops; } } } /******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this * distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: IBM Corporation - initial API and implementation *******************************************************************************/ class StyledTextRenderer { StyleRange[] stylesSet; int stylesSetCount = 0; int[] ranges; int styleCount; StyleRange[] styles; boolean hasLinks; LineInfo[] lines; int lineCount; int[] lineWidth; int[] lineHeight; final static boolean COMPACT_STYLES = true; final static boolean MERGE_STYLES = true; final static int GROW = 32; void setStyleRanges(int[] newRanges, StyleRange[] newStyles) { int[] _newRanges = newRanges; StyleRange[] _newStyles = newStyles; if (_newStyles == null) { this.stylesSetCount = this.styleCount = 0; this.ranges = null; this.styles = null; this.stylesSet = null; this.hasLinks = false; return; } if (_newRanges == null && COMPACT_STYLES) { _newRanges = new int[_newStyles.length << 1]; StyleRange[] tmpStyles = new StyleRange[_newStyles.length]; if (this.stylesSet == null) this.stylesSet = new StyleRange[4]; for (int i = 0, j = 0; i < _newStyles.length; i++) { StyleRange newStyle = _newStyles[i]; _newRanges[j++] = newStyle.start; _newRanges[j++] = newStyle.length; int index = 0; while (index < this.stylesSetCount) { if (this.stylesSet[index].similarTo(newStyle)) break; index++; } if (index == this.stylesSetCount) { if (this.stylesSetCount == this.stylesSet.length) { StyleRange[] tmpStylesSet = new StyleRange[this.stylesSetCount + 4]; System.arraycopy(this.stylesSet, 0, tmpStylesSet, 0, this.stylesSetCount); this.stylesSet = tmpStylesSet; } this.stylesSet[this.stylesSetCount++] = newStyle; } tmpStyles[i] = this.stylesSet[index]; } _newStyles = tmpStyles; } if (this.styleCount == 0) { if (_newRanges != null) { this.ranges = new int[_newRanges.length]; System.arraycopy(_newRanges, 0, this.ranges, 0, this.ranges.length); } this.styles = new StyleRange[_newStyles.length]; System.arraycopy(_newStyles, 0, this.styles, 0, this.styles.length); this.styleCount = _newStyles.length; return; } if (_newRanges != null && this.ranges == null) { this.ranges = new int[this.styles.length << 1]; for (int i = 0, j = 0; i < this.styleCount; i++) { this.ranges[j++] = this.styles[i].start; this.ranges[j++] = this.styles[i].length; } } if (_newRanges == null && this.ranges != null) { _newRanges = new int[_newStyles.length << 1]; for (int i = 0, j = 0; i < _newStyles.length; i++) { _newRanges[j++] = _newStyles[i].start; _newRanges[j++] = _newStyles[i].length; } } if (this.ranges != null && _newRanges != null) { int rangeCount = this.styleCount << 1; int start = _newRanges[0]; int modifyStart = getRangeIndex(start, -1, rangeCount), modifyEnd; boolean insert = modifyStart == rangeCount; if (!insert) { int end = _newRanges[_newRanges.length - 2] + _newRanges[_newRanges.length - 1]; modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount); insert = modifyStart == modifyEnd && this.ranges[modifyStart] >= end; } if (insert) { addMerge(_newRanges, _newStyles, _newRanges.length, modifyStart, modifyStart); return; } modifyEnd = modifyStart; int[] mergeRanges = new int[6]; StyleRange[] mergeStyles = new StyleRange[3]; for (int i = 0; i < _newRanges.length; i += 2) { int newStart = _newRanges[i]; int newEnd = newStart + _newRanges[i + 1]; if (newStart == newEnd) continue; int modifyLast = 0, mergeCount = 0; while (modifyEnd < rangeCount) { if (newStart >= this.ranges[modifyStart] + this.ranges[modifyStart + 1]) modifyStart += 2; if (this.ranges[modifyEnd] + this.ranges[modifyEnd + 1] > newEnd) break; modifyEnd += 2; } if (this.ranges[modifyStart] < newStart && newStart < this.ranges[modifyStart] + this.ranges[modifyStart + 1]) { mergeStyles[mergeCount >> 1] = this.styles[modifyStart >> 1]; mergeRanges[mergeCount] = this.ranges[modifyStart]; mergeRanges[mergeCount + 1] = newStart - this.ranges[modifyStart]; mergeCount += 2; } mergeStyles[mergeCount >> 1] = _newStyles[i >> 1]; mergeRanges[mergeCount] = newStart; mergeRanges[mergeCount + 1] = _newRanges[i + 1]; mergeCount += 2; if (modifyEnd < rangeCount && this.ranges[modifyEnd] < newEnd && newEnd < this.ranges[modifyEnd] + this.ranges[modifyEnd + 1]) { mergeStyles[mergeCount >> 1] = this.styles[modifyEnd >> 1]; mergeRanges[mergeCount] = newEnd; mergeRanges[mergeCount + 1] = this.ranges[modifyEnd] + this.ranges[modifyEnd + 1] - newEnd; mergeCount += 2; modifyLast = 2; } int grow = addMerge(mergeRanges, mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast); rangeCount += grow; modifyStart = modifyEnd += grow; } } else { int start = _newStyles[0].start; int modifyStart = getRangeIndex(start, -1, this.styleCount), modifyEnd; boolean insert = modifyStart == this.styleCount; if (!insert) { int end = _newStyles[_newStyles.length - 1].start + _newStyles[_newStyles.length - 1].length; modifyEnd = getRangeIndex(end, modifyStart - 1, this.styleCount); insert = modifyStart == modifyEnd && this.styles[modifyStart].start >= end; } if (insert) { addMerge(_newStyles, _newStyles.length, modifyStart, modifyStart); return; } modifyEnd = modifyStart; StyleRange[] mergeStyles = new StyleRange[3]; for (int i = 0; i < _newStyles.length; i++) { StyleRange newStyle = _newStyles[i], style; int newStart = newStyle.start; int newEnd = newStart + newStyle.length; if (newStart == newEnd) continue; int modifyLast = 0, mergeCount = 0; while (modifyEnd < this.styleCount) { if (newStart >= this.styles[modifyStart].start + this.styles[modifyStart].length) modifyStart++; if (this.styles[modifyEnd].start + this.styles[modifyEnd].length > newEnd) break; modifyEnd++; } style = this.styles[modifyStart]; if (style.start < newStart && newStart < style.start + style.length) { style = mergeStyles[mergeCount++] = (StyleRange) style.clone(); style.length = newStart - style.start; } mergeStyles[mergeCount++] = newStyle; if (modifyEnd < this.styleCount) { style = this.styles[modifyEnd]; if (style.start < newEnd && newEnd < style.start + style.length) { style = mergeStyles[mergeCount++] = (StyleRange) style.clone(); style.length += style.start - newEnd; style.start = newEnd; modifyLast = 1; } } int grow = addMerge(mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast); modifyStart = modifyEnd += grow; } } } int[] getRanges(int start, int length) { if (length == 0) return null; int[] newRanges; int end = start + length - 1; if (this.ranges != null) { int rangeCount = this.styleCount << 1; int rangeStart = getRangeIndex(start, -1, rangeCount); if (rangeStart >= rangeCount) return null; if (this.ranges[rangeStart] > end) return null; int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount)); if (this.ranges[rangeEnd] > end) rangeEnd = Math.max(rangeStart, rangeEnd - 2); newRanges = new int[rangeEnd - rangeStart + 2]; System.arraycopy(this.ranges, rangeStart, newRanges, 0, newRanges.length); } else { int rangeStart = getRangeIndex(start, -1, this.styleCount); if (rangeStart >= this.styleCount) return null; if (this.styles[rangeStart].start > end) return null; int rangeEnd = Math.min(this.styleCount - 1, getRangeIndex(end, rangeStart - 1, this.styleCount)); if (this.styles[rangeEnd].start > end) rangeEnd = Math.max(rangeStart, rangeEnd - 1); newRanges = new int[(rangeEnd - rangeStart + 1) << 1]; for (int i = rangeStart, j = 0; i <= rangeEnd; i++, j += 2) { StyleRange style = this.styles[i]; newRanges[j] = style.start; newRanges[j + 1] = style.length; } } if (start > newRanges[0]) { newRanges[1] = newRanges[0] + newRanges[1] - start; newRanges[0] = start; } if (end < newRanges[newRanges.length - 2] + newRanges[newRanges.length - 1] - 1) { newRanges[newRanges.length - 1] = end - newRanges[newRanges.length - 2] + 1; } return newRanges; } int getRangeIndex(int offset, int low, int high) { int _low = low; int _high = high; if (this.styleCount == 0) return 0; if (this.ranges != null) { while (_high - _low > 2) { int index = ((_high + _low) / 2) / 2 * 2; int end = this.ranges[index] + this.ranges[index + 1]; if (end > offset) { _high = index; } else { _low = index; } } } else { while (_high - _low > 1) { int index = ((_high + _low) / 2); int end = this.styles[index].start + this.styles[index].length; if (end > offset) { _high = index; } else { _low = index; } } } return _high; } void textChanging(TextChangingEvent event) { int start = event.offset; int newCharCount = event.newCharCount, replaceCharCount = event.replaceCharCount; @SuppressWarnings("unused") int newLineCount = event.newLineCount, replaceLineCount = event.replaceLineCount; updateRanges(start, replaceCharCount, newCharCount); // // int startLine = getContent().getLineAtOffset(start); // if (replaceCharCount == getContent().getCharCount()) lines = // null; // if (replaceLineCount == lineCount) { // lineCount = newLineCount; // lineWidth = new int[lineCount]; // lineHeight = new int[lineCount]; // reset(0, lineCount); // } else { // int delta = newLineCount - replaceLineCount; // if (lineCount + delta > lineWidth.length) { // int[] newWidths = new int[lineCount + delta + GROW]; // System.arraycopy(lineWidth, 0, newWidths, 0, lineCount); // lineWidth = newWidths; // int[] newHeights = new int[lineCount + delta + GROW]; // System.arraycopy(lineHeight, 0, newHeights, 0, lineCount); // lineHeight = newHeights; // } // if (lines != null) { // if (lineCount + delta > lines.length) { // LineInfo[] newLines = new LineInfo[lineCount + delta + GROW]; // System.arraycopy(lines, 0, newLines, 0, lineCount); // lines = newLines; // } // } // int startIndex = startLine + replaceLineCount + 1; // int endIndex = startLine + newLineCount + 1; // System.arraycopy(lineWidth, startIndex, lineWidth, endIndex, // lineCount - startIndex); // System.arraycopy(lineHeight, startIndex, lineHeight, endIndex, // lineCount - startIndex); // for (int i = startLine; i < endIndex; i++) { // lineWidth[i] = lineHeight[i] = -1; // } // for (int i = lineCount + delta; i < lineCount; i++) { // lineWidth[i] = lineHeight[i] = -1; // } // if (layouts != null) { // int layoutStartLine = startLine - topIndex; // int layoutEndLine = layoutStartLine + replaceLineCount + 1; // for (int i = layoutStartLine; i < layoutEndLine; i++) { // if (0 <= i && i < layouts.length) { // if (layouts[i] != null) layouts[i].dispose(); // layouts[i] = null; // if (bullets != null && bulletsIndices != null) bullets[i] = null; // } // } // if (delta > 0) { // for (int i = layouts.length - 1; i >= layoutEndLine; i--) { // if (0 <= i && i < layouts.length) { // endIndex = i + delta; // if (0 <= endIndex && endIndex < layouts.length) { // layouts[endIndex] = layouts[i]; // layouts[i] = null; // if (bullets != null && bulletsIndices != null) { // bullets[endIndex] = bullets[i]; // bulletsIndices[endIndex] = bulletsIndices[i]; // bullets[i] = null; // } // } else { // if (layouts[i] != null) layouts[i].dispose(); // layouts[i] = null; // if (bullets != null && bulletsIndices != null) bullets[i] = null; // } // } // } // } else if (delta < 0) { // for (int i = layoutEndLine; i < layouts.length; i++) { // if (0 <= i && i < layouts.length) { // endIndex = i + delta; // if (0 <= endIndex && endIndex < layouts.length) { // layouts[endIndex] = layouts[i]; // layouts[i] = null; // if (bullets != null && bulletsIndices != null) { // bullets[endIndex] = bullets[i]; // bulletsIndices[endIndex] = bulletsIndices[i]; // bullets[i] = null; // } // } else { // if (layouts[i] != null) layouts[i].dispose(); // layouts[i] = null; // if (bullets != null && bulletsIndices != null) bullets[i] = null; // } // } // } // } // } // if (replaceLineCount != 0 || newLineCount != 0) { // int startLineOffset = getContent().getOffsetAtLine(startLine); // if (startLineOffset != start) startLine++; // updateBullets(startLine, replaceLineCount, newLineCount, true); // if (lines != null) { // startIndex = startLine + replaceLineCount; // endIndex = startLine + newLineCount; // System.arraycopy(lines, startIndex, lines, endIndex, lineCount - // startIndex); // for (int i = startLine; i < endIndex; i++) { // lines[i] = null; // } // for (int i = lineCount + delta; i < lineCount; i++) { // lines[i] = null; // } // } // } // lineCount += delta; // if (maxWidthLineIndex != -1 && startLine <= maxWidthLineIndex && // maxWidthLineIndex <= startLine + replaceLineCount) { // maxWidth = 0; // maxWidthLineIndex = -1; // for (int i = 0; i < lineCount; i++) { // if (lineWidth[i] > maxWidth) { // maxWidth = lineWidth[i]; // maxWidthLineIndex = i; // } // } // } // } } int addMerge(int[] mergeRanges, StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) { int _mergeCount = mergeCount; int _modifyEnd = modifyEnd; int rangeCount = this.styleCount << 1; StyleRange endStyle = null; int endStart = 0, endLength = 0; if (_modifyEnd < rangeCount) { endStyle = this.styles[_modifyEnd >> 1]; endStart = this.ranges[_modifyEnd]; endLength = this.ranges[_modifyEnd + 1]; } int grow = _mergeCount - (_modifyEnd - modifyStart); if (rangeCount + grow >= this.ranges.length) { int[] tmpRanges = new int[this.ranges.length + grow + (GROW << 1)]; System.arraycopy(this.ranges, 0, tmpRanges, 0, modifyStart); StyleRange[] tmpStyles = new StyleRange[this.styles.length + (grow >> 1) + GROW]; System.arraycopy(this.styles, 0, tmpStyles, 0, modifyStart >> 1); if (rangeCount > _modifyEnd) { System.arraycopy(this.ranges, _modifyEnd, tmpRanges, modifyStart + _mergeCount, rangeCount - _modifyEnd); System.arraycopy(this.styles, _modifyEnd >> 1, tmpStyles, (modifyStart + _mergeCount) >> 1, this.styleCount - (_modifyEnd >> 1)); } this.ranges = tmpRanges; this.styles = tmpStyles; } else { if (rangeCount > _modifyEnd) { System.arraycopy(this.ranges, _modifyEnd, this.ranges, modifyStart + _mergeCount, rangeCount - _modifyEnd); System.arraycopy(this.styles, _modifyEnd >> 1, this.styles, (modifyStart + _mergeCount) >> 1, this.styleCount - (_modifyEnd >> 1)); } } if (MERGE_STYLES) { int j = modifyStart; for (int i = 0; i < _mergeCount; i += 2) { if (j > 0 && this.ranges[j - 2] + this.ranges[j - 1] == mergeRanges[i] && mergeStyles[i >> 1].similarTo(this.styles[(j - 2) >> 1])) { this.ranges[j - 1] += mergeRanges[i + 1]; } else { this.styles[j >> 1] = mergeStyles[i >> 1]; this.ranges[j++] = mergeRanges[i]; this.ranges[j++] = mergeRanges[i + 1]; } } if (endStyle != null && this.ranges[j - 2] + this.ranges[j - 1] == endStart && endStyle.similarTo(this.styles[(j - 2) >> 1])) { this.ranges[j - 1] += endLength; _modifyEnd += 2; _mergeCount += 2; } if (rangeCount > _modifyEnd) { System.arraycopy(this.ranges, modifyStart + _mergeCount, this.ranges, j, rangeCount - _modifyEnd); System.arraycopy(this.styles, (modifyStart + _mergeCount) >> 1, this.styles, j >> 1, this.styleCount - (_modifyEnd >> 1)); } grow = (j - modifyStart) - (_modifyEnd - modifyStart); } else { System.arraycopy(mergeRanges, 0, this.ranges, modifyStart, _mergeCount); System.arraycopy(mergeStyles, 0, this.styles, modifyStart >> 1, _mergeCount >> 1); } this.styleCount += grow >> 1; return grow; } int addMerge(StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) { int _mergeCount = mergeCount; int _modifyEnd = modifyEnd; int grow = _mergeCount - (_modifyEnd - modifyStart); StyleRange endStyle = null; if (_modifyEnd < this.styleCount) endStyle = this.styles[_modifyEnd]; if (this.styleCount + grow >= this.styles.length) { StyleRange[] tmpStyles = new StyleRange[this.styles.length + grow + GROW]; System.arraycopy(this.styles, 0, tmpStyles, 0, modifyStart); if (this.styleCount > _modifyEnd) { System.arraycopy(this.styles, _modifyEnd, tmpStyles, modifyStart + _mergeCount, this.styleCount - _modifyEnd); } this.styles = tmpStyles; } else { if (this.styleCount > _modifyEnd) { System.arraycopy(this.styles, _modifyEnd, this.styles, modifyStart + _mergeCount, this.styleCount - _modifyEnd); } } if (MERGE_STYLES) { int j = modifyStart; for (int i = 0; i < _mergeCount; i++) { StyleRange newStyle = mergeStyles[i], style; if (j > 0 && (style = this.styles[j - 1]).start + style.length == newStyle.start && newStyle.similarTo(style)) { style.length += newStyle.length; } else { this.styles[j++] = newStyle; } } StyleRange style = this.styles[j - 1]; if (endStyle != null && style.start + style.length == endStyle.start && endStyle.similarTo(style)) { style.length += endStyle.length; _modifyEnd++; _mergeCount++; } if (this.styleCount > _modifyEnd) { System.arraycopy(this.styles, modifyStart + _mergeCount, this.styles, j, this.styleCount - _modifyEnd); } grow = (j - modifyStart) - (_modifyEnd - modifyStart); } else { System.arraycopy(mergeStyles, 0, this.styles, modifyStart, _mergeCount); } this.styleCount += grow; return grow; } void updateRanges(int start, int replaceCharCount, int newCharCount) { if (this.styleCount == 0 || (replaceCharCount == 0 && newCharCount == 0)) return; if (this.ranges != null) { int rangeCount = this.styleCount << 1; int modifyStart = getRangeIndex(start, -1, rangeCount); if (modifyStart == rangeCount) return; int end = start + replaceCharCount; int modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount); int offset = newCharCount - replaceCharCount; if (modifyStart == modifyEnd && this.ranges[modifyStart] < start && end < this.ranges[modifyEnd] + this.ranges[modifyEnd + 1]) { if (newCharCount == 0) { this.ranges[modifyStart + 1] -= replaceCharCount; modifyEnd += 2; } else { if (rangeCount + 2 > this.ranges.length) { int[] newRanges = new int[this.ranges.length + (GROW << 1)]; System.arraycopy(this.ranges, 0, newRanges, 0, rangeCount); this.ranges = newRanges; StyleRange[] newStyles = new StyleRange[this.styles.length + GROW]; System.arraycopy(this.styles, 0, newStyles, 0, this.styleCount); this.styles = newStyles; } System.arraycopy(this.ranges, modifyStart + 2, this.ranges, modifyStart + 4, rangeCount - (modifyStart + 2)); System.arraycopy(this.styles, (modifyStart + 2) >> 1, this.styles, (modifyStart + 4) >> 1, this.styleCount - ((modifyStart + 2) >> 1)); this.ranges[modifyStart + 3] = this.ranges[modifyStart] + this.ranges[modifyStart + 1] - end; this.ranges[modifyStart + 2] = start + newCharCount; this.ranges[modifyStart + 1] = start - this.ranges[modifyStart]; this.styles[(modifyStart >> 1) + 1] = this.styles[modifyStart >> 1]; rangeCount += 2; this.styleCount++; modifyEnd += 4; } if (offset != 0) { for (int i = modifyEnd; i < rangeCount; i += 2) { this.ranges[i] += offset; } } } else { if (this.ranges[modifyStart] < start && start < this.ranges[modifyStart] + this.ranges[modifyStart + 1]) { this.ranges[modifyStart + 1] = start - this.ranges[modifyStart]; modifyStart += 2; } if (modifyEnd < rangeCount && this.ranges[modifyEnd] < end && end < this.ranges[modifyEnd] + this.ranges[modifyEnd + 1]) { this.ranges[modifyEnd + 1] = this.ranges[modifyEnd] + this.ranges[modifyEnd + 1] - end; this.ranges[modifyEnd] = end; } if (offset != 0) { for (int i = modifyEnd; i < rangeCount; i += 2) { this.ranges[i] += offset; } } System.arraycopy(this.ranges, modifyEnd, this.ranges, modifyStart, rangeCount - modifyEnd); System.arraycopy(this.styles, modifyEnd >> 1, this.styles, modifyStart >> 1, this.styleCount - (modifyEnd >> 1)); this.styleCount -= (modifyEnd - modifyStart) >> 1; } } else { int modifyStart = getRangeIndex(start, -1, this.styleCount); if (modifyStart == this.styleCount) return; int end = start + replaceCharCount; int modifyEnd = getRangeIndex(end, modifyStart - 1, this.styleCount); int offset = newCharCount - replaceCharCount; if (modifyStart == modifyEnd && this.styles[modifyStart].start < start && end < this.styles[modifyEnd].start + this.styles[modifyEnd].length) { if (newCharCount == 0) { this.styles[modifyStart].length -= replaceCharCount; modifyEnd++; } else { if (this.styleCount + 1 > this.styles.length) { StyleRange[] newStyles = new StyleRange[this.styles.length + GROW]; System.arraycopy(this.styles, 0, newStyles, 0, this.styleCount); this.styles = newStyles; } System.arraycopy(this.styles, modifyStart + 1, this.styles, modifyStart + 2, this.styleCount - (modifyStart + 1)); this.styles[modifyStart + 1] = (StyleRange) this.styles[modifyStart].clone(); this.styles[modifyStart + 1].length = this.styles[modifyStart].start + this.styles[modifyStart].length - end; this.styles[modifyStart + 1].start = start + newCharCount; this.styles[modifyStart].length = start - this.styles[modifyStart].start; this.styleCount++; modifyEnd += 2; } if (offset != 0) { for (int i = modifyEnd; i < this.styleCount; i++) { this.styles[i].start += offset; } } } else { if (this.styles[modifyStart].start < start && start < this.styles[modifyStart].start + this.styles[modifyStart].length) { this.styles[modifyStart].length = start - this.styles[modifyStart].start; modifyStart++; } if (modifyEnd < this.styleCount && this.styles[modifyEnd].start < end && end < this.styles[modifyEnd].start + this.styles[modifyEnd].length) { this.styles[modifyEnd].length = this.styles[modifyEnd].start + this.styles[modifyEnd].length - end; this.styles[modifyEnd].start = end; } if (offset != 0) { for (int i = modifyEnd; i < this.styleCount; i++) { this.styles[i].start += offset; } } System.arraycopy(this.styles, modifyEnd, this.styles, modifyStart, this.styleCount - modifyEnd); this.styleCount -= modifyEnd - modifyStart; } } } StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) { if (length == 0) return null; StyleRange[] newStyles; int end = start + length - 1; if (this.ranges != null) { int rangeCount = this.styleCount << 1; int rangeStart = getRangeIndex(start, -1, rangeCount); if (rangeStart >= rangeCount) return null; if (this.ranges[rangeStart] > end) return null; int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount)); if (this.ranges[rangeEnd] > end) rangeEnd = Math.max(rangeStart, rangeEnd - 2); newStyles = new StyleRange[((rangeEnd - rangeStart) >> 1) + 1]; if (includeRanges) { for (int i = rangeStart, j = 0; i <= rangeEnd; i += 2, j++) { newStyles[j] = (StyleRange) this.styles[i >> 1].clone(); newStyles[j].start = this.ranges[i]; newStyles[j].length = this.ranges[i + 1]; } } else { System.arraycopy(this.styles, rangeStart >> 1, newStyles, 0, newStyles.length); } } else { int rangeStart = getRangeIndex(start, -1, this.styleCount); if (rangeStart >= this.styleCount) return null; if (this.styles[rangeStart].start > end) return null; int rangeEnd = Math.min(this.styleCount - 1, getRangeIndex(end, rangeStart - 1, this.styleCount)); if (this.styles[rangeEnd].start > end) rangeEnd = Math.max(rangeStart, rangeEnd - 1); newStyles = new StyleRange[rangeEnd - rangeStart + 1]; System.arraycopy(this.styles, rangeStart, newStyles, 0, newStyles.length); } if (includeRanges || this.ranges == null) { StyleRange style = newStyles[0]; if (start > style.start) { newStyles[0] = style = (StyleRange) style.clone(); style.length = style.start + style.length - start; style.start = start; } style = newStyles[newStyles.length - 1]; if (end < style.start + style.length - 1) { newStyles[newStyles.length - 1] = style = (StyleRange) style.clone(); style.length = end - style.start + 1; } } return newStyles; } } // public void setTabs(int tabWidth) { // // keep empty // } // // public void setRedraw(boolean b) { // // keep empty // } /** * @return the current selection */ public @NonNull TextSelection getSelection() { TextSelection textSelection = this.currentSelection.get(); if (textSelection == null) { textSelection = new TextSelection(getCaretOffset(), 0); } return textSelection; } /** * @return the selection property */ public @NonNull ObjectProperty selectionProperty() { return this.currentSelection; } /** * Set the current selection * * @param selection * the selection */ public void setSelection(@NonNull TextSelection selection) { if (selection.length == 0) { setCaretOffset(selection.offset); } else { // this.caretOffsetProperty.set(selection.offset+selection.length); this.currentSelection.set(selection); } } /** * Clear the current selection */ public void clearSelection() { this.currentSelection.set(null); } /** * Set the selection * * @param offset * the offset * @param length * the length */ public void setSelectionRange(int offset, int length) { setSelection(new TextSelection(offset, length)); } @NonNull private final BooleanProperty editableProperty = new SimpleBooleanProperty(this, "editableProperty", true); //$NON-NLS-1$ /** * Mark the editor editable * *

* Default is true *

* * @param editable * the new value */ public void setEditable(boolean editable) { editableProperty().set(editable); } /** * Check if editable *

* Default is true *

* * @return the current value */ public boolean getEditable() { return editableProperty().get(); } /** * The editable property *

* Default is true *

* * @return the property */ public @NonNull BooleanProperty editableProperty() { return this.editableProperty; } /** * Check the location at the given offset * * @param offset * the offset * @return the point */ public @Nullable Point2D getLocationAtOffset(int offset) { if (offset < 0 || offset > getCharCount()) { throw new IllegalArgumentException(); } return ((StyledTextSkin) getSkin()).getCaretLocation(offset); } /** * Get the line height * * @param offset * the offset * @return the height */ public double getLineHeight(int offset) { if (offset < 0 || offset > getCharCount()) { throw new IllegalArgumentException(); } return ((StyledTextSkin) getSkin()).getLineHeight(offset); } // public void showSelection() { // // TODO Auto-generated method stub // // } /** * Get the line index for the caret * * @param caretOffset * the caret offset * @return the line index */ public int getLineAtOffset(int caretOffset) { return getContent().getLineAtOffset(caretOffset); } /** * Get the initial offset of the line * * @param lineNumber * the line number * @return the offset */ public int getOffsetAtLine(int lineNumber) { return getContent().getOffsetAtLine(lineNumber); } /** * Get the text for the given range * * @param start * the start * @param end * the end * @return the text */ public @NonNull String getText(int start, int end) { return getContent().getTextRange(start, end - start + 1); } /** * Paste the clipboard content */ public void paste() { final Clipboard clipboard = Clipboard.getSystemClipboard(); if (clipboard.hasString()) { final String text = clipboard.getString(); if (text != null) { // TODO Once we have a real selection we need getContent().replaceTextRange(getCaretOffset(), 0, text); setCaretOffset(getCaretOffset() + text.length()); } } } /** * Copy the current selection into the clipboard */ @SuppressWarnings("null") public void copy() { if (getSelection().length > 0) { final Clipboard clipboard = Clipboard.getSystemClipboard(); clipboard.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, getContent().getTextRange(getSelection().offset, getSelection().length))); } } /** * @return property holding the factory to create graphics in the ruler */ @NonNull public final ObjectProperty<@NonNull Callback<@NonNull StyledTextLine, @Nullable Node>> lineRulerGraphicNodeFactoryProperty() { return this.lineRulerGraphicNodeFactory; } /** * @return the current factory to create graphics in the ruler */ public final @NonNull Callback<@NonNull StyledTextLine, @Nullable Node> getLineRulerGraphicNodeFactory() { return this.lineRulerGraphicNodeFactoryProperty().get(); } /** * Set a new factory to create graphics in the ruler * * @param lineRulerGraphicNodeFactory * the factory */ public final void setLineRulerGraphicNodeFactory(final @NonNull Callback<@NonNull StyledTextLine, @Nullable Node> lineRulerGraphicNodeFactory) { this.lineRulerGraphicNodeFactoryProperty().set(lineRulerGraphicNodeFactory); } /** * Refresh the line ruler */ public void refreshLineRuler() { if (getSkin() != null) { // TODO We need to send an event! ((StyledTextSkin) getSkin()).refreshLineRuler(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy