小程序richtext
- 它应该是本地SWT组件,而不是Eclipse View或Editor,并且必须可嵌入任何SWT组合中。
- 它应允许使用粗体,斜体,删除线等基本格式。
- 它应该支持剪贴板的基本操作,例如复制,剪切和粘贴。
- 它应该使用基本HTML输出文本。
- 它需要能够解析所生成的基本HTML,以允许版本。
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CaretListener;
import org.eclipse.swt.custom.ExtendedModifyEvent;
import org.eclipse.swt.custom.ExtendedModifyListener;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Caret;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.xml.sax.SAXException;
public class RichText extends Composite {
private List cachedStyles =
Collections.synchronizedList(new LinkedList());
private ToolBar toolBar;
private StyledText styledText;
private ToolItem boldBtn;
private ToolItem italicBtn;
private ToolItem strikeThroughBtn;
private ToolItem underlineBtn;
private ToolItem pasteBtn;
private ToolItem eraserBtn;
public RichText(Composite parent, int style) {
super(parent, style);
initComponents();
}
public void addCaretListener(CaretListener listener) {
styledText.addCaretListener(listener);
}
public void removeCaretListener(CaretListener listener) {
styledText.removeCaretListener(listener);
}
public void addExtendedModifyListener(ExtendedModifyListener listener) {
styledText.addExtendedModifyListener(listener);
}
public void removeExtendedModifyListener(ExtendedModifyListener listener) {
styledText.removeExtendedModifyListener(listener);
}
public void addModifyListener(ModifyListener listener) {
styledText.addModifyListener(listener);
}
public void removeModifyListener(ModifyListener listener) {
styledText.removeModifyListener(listener);
}
public void addVerifyKeyListener(VerifyKeyListener listener) {
styledText.addVerifyKeyListener(listener);
}
public void removeVerifyKeyListener(VerifyKeyListener listener) {
styledText.removeVerifyKeyListener(listener);
}
public void addVerifyListener(VerifyListener listener) {
styledText.addVerifyListener(listener);
}
public void removeVerifyListener(VerifyListener listener) {
styledText.removeVerifyListener(listener);
}
public int getCharCount() {
return styledText.getCharCount();
}
public Caret getCaret() {
return styledText.getCaret();
}
public int getCaretOffset() {
return styledText.getCaretOffset();
}
/**
* Obtain an HTML formatted text from the component contents
*
* @return an HTML formatted text
*/
public String getFormattedText() {
String plainText = styledText.getText();
RichStringBuilder builder = new RichStringBuilder();
Integer[] lineBreaks = getLineBreaks();
int brIdx = 0;
int start = 0;
int end = (lineBreaks.length > brIdx ? lineBreaks[brIdx++] : plainText.length() - 1);
while (start < end) {
builder.startParagraph();
StyleRange[] ranges = styledText.getStyleRanges(start, (end - start));
if (ranges != null && ranges.length > 0) {
for (int i = 0;i < ranges.length;i++) {
if (start < ranges[i].start) {
builder.append(plainText.substring(start, ranges[i].start));
}
List styles = translateStyle(ranges[i]);
builder.startFontStyles(styles.toArray(new FontStyle[styles.size()]));
builder.append(plainText.substring(ranges[i].start,
ranges[i].start + ranges[i].length));
builder.endFontStyles(styles.size());
start = (ranges[i].start + ranges[i].length) + 1;
}
}
if (start < end) {
builder.append(plainText.substring(start, end));
}
start = end + styledText.getLineDelimiter().length();
end = (lineBreaks.length > brIdx ? lineBreaks[brIdx++] : plainText.length() - 1);
builder.endParagraph();
}
return builder.toString();
}
public void setFormattedText(String text)
throws ParserConfigurationException, SAXException, IOException {
RichTextParser parser = RichTextParser.parse(text);
styledText.setText(parser.getText());
styledText.setStyleRanges(parser.getStyleRanges());
}
public int getLineAtOffset(int offset) {
return styledText.getLineAtOffset(offset);
}
public int getLineCount() {
return styledText.getLineCount();
}
public int getLineSpacing() {
return styledText.getLineSpacing();
}
public String getText() {
return styledText.getText();
}
protected void applyFontStyleToSelection(FontStyle style) {
Point sel = styledText.getSelectionRange();
if ((sel == null) || (sel.y == 0)) {
return ;
}
StyleRange newStyle;
for (int i = sel.x; i < (sel.x + sel.y); i++) {
StyleRange range = styledText.getStyleRangeAtOffset(i);
if (range != null) {
newStyle = (StyleRange) range.clone();
newStyle.start = i;
newStyle.length = 1;
} else {
newStyle = new StyleRange(i, 1, null, null, SWT.NORMAL);
}
switch (style) {
case BOLD:
newStyle.fontStyle ^= SWT.BOLD;
break;
case ITALIC:
newStyle.fontStyle ^= SWT.ITALIC;
break;
case STRIKE_THROUGH:
newStyle.strikeout = !newStyle.strikeout;
break;
case UNDERLINE:
newStyle.underline = !newStyle.underline;
break;
}
styledText.setStyleRange(newStyle);
}
styledText.setSelectionRange(sel.x + sel.y, 0);
}
/**
* Clear all styled data
*/
protected void clearStylesFromSelection() {
Point sel = styledText.getSelectionRange();
if ((sel != null) && (sel.y != 0)) {
StyleRange style = new StyleRange(
sel.x, sel.y, null, null, SWT.NORMAL);
styledText.setStyleRange(style);
}
styledText.setSelectionRange(sel.x + sel.y, 0);
}
private Integer[] getLineBreaks() {
List list = new ArrayList();
int lastIdx = 0;
while (lastIdx < styledText.getCharCount()) {
int br = styledText.getText().indexOf(
styledText.getLineDelimiter(), lastIdx);
if (br >= lastIdx && !list.contains(br)) {
list.add(br);
}
lastIdx += styledText.getLineDelimiter().length() + 1;
}
Collections.sort(list);
return list.toArray(new Integer[list.size()]);
}
protected void handleCutCopy() {
// Save the cut/copied style info so that during paste we will maintain
// the style information. Cut/copied text is put in the clipboard in
// RTF format, but is not pasted in RTF format. The other way to
// handle the pasting of styles would be to access the Clipboard
// directly and
// parse the RTF text.
cachedStyles = Collections
.synchronizedList(new LinkedList());
Point sel = styledText.getSelectionRange();
int startX = sel.x;
for (int i = sel.x; i <= sel.x + sel.y - 1; i++) {
StyleRange style = styledText.getStyleRangeAtOffset(i);
if (style != null) {
style.start = style.start - startX;
if (!cachedStyles.isEmpty()) {
StyleRange lastStyle = cachedStyles
.get(cachedStyles.size() - 1);
if (lastStyle.similarTo(style)
&& lastStyle.start + lastStyle.length == style.start) {
lastStyle.length++;
} else {
cachedStyles.add(style);
}
} else {
cachedStyles.add(style);
}
}
}
pasteBtn.setEnabled(true);
}
private void handleExtendedModified(ExtendedModifyEvent event) {
if (event.length == 0) return;
StyleRange style;
if (event.length == 1
|| styledText.getTextRange(event.start, event.length).equals(
styledText.getLineDelimiter())) {
// Have the new text take on the style of the text to its right
// (during
// typing) if no style information is active.
int caretOffset = styledText.getCaretOffset();
style = null;
if (caretOffset < styledText.getCharCount())
style = styledText.getStyleRangeAtOffset(caretOffset);
if (style != null) {
style = (StyleRange) style.clone();
style.start = event.start;
style.length = event.length;
} else {
style = new StyleRange(event.start, event.length, null, null,
SWT.NORMAL);
}
if (boldBtn.getSelection())
style.fontStyle |= SWT.BOLD;
if (italicBtn.getSelection())
style.fontStyle |= SWT.ITALIC;
style.underline = underlineBtn.getSelection();
style.strikeout = strikeThroughBtn.getSelection();
if (!style.isUnstyled())
styledText.setStyleRange(style);
} else {
// paste occurring, have text take on the styles it had when it was
// cut/copied
for (int i = 0; i < cachedStyles.size(); i++) {
style = cachedStyles.get(i);
StyleRange newStyle = (StyleRange) style.clone();
newStyle.start = style.start + event.start;
styledText.setStyleRange(newStyle);
}
}
}
private void handleTextSelected(SelectionEvent event) {
Point sel = styledText.getSelectionRange();
if ((sel != null) && (sel.y != 0)) {
StyleRange[] styles = styledText.getStyleRanges(sel.x, sel.y);
eraserBtn.setEnabled((styles != null) && (styles.length > 0));
} else {
eraserBtn.setEnabled(false);
}
}
private void handleKeyReleased(KeyEvent event) {
if ((event.keyCode == SWT.ARROW_LEFT) || (event.keyCode == SWT.ARROW_UP)
|| (event.keyCode == SWT.ARROW_RIGHT) || (event.keyCode == SWT.ARROW_DOWN)) {
updateStyleButtons();
}
}
private void updateStyleButtons() {
int caretOffset = styledText.getCaretOffset();
StyleRange style = null;
if (caretOffset >= 0 && caretOffset < styledText.getCharCount()) {
style = styledText.getStyleRangeAtOffset(caretOffset);
}
if (style != null) {
boldBtn.setSelection((style.fontStyle & SWT.BOLD) != 0);
italicBtn.setSelection((style.fontStyle & SWT.ITALIC) != 0);
underlineBtn.setSelection(style.underline);
strikeThroughBtn.setSelection(style.strikeout);
} else {
boldBtn.setSelection(false);
italicBtn.setSelection(false);
underlineBtn.setSelection(false);
strikeThroughBtn.setSelection(false);
}
}
private void initComponents() {
GridLayout layout = new GridLayout();
layout.numColumns = 1;
setLayout(layout);
toolBar = createToolBar(this);
toolBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
styledText = new StyledText(this, SWT.BORDER | SWT.MULTI |
SWT.V_SCROLL | SWT.H_SCROLL);
styledText.setLayoutData(new GridData(GridData.FILL_BOTH));
styledText.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
handleKeyReleased(e);
}
});
styledText.addExtendedModifyListener(new ExtendedModifyListener() {
@Override
public void modifyText(ExtendedModifyEvent event) {
handleExtendedModified(event);
}
});
styledText.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
updateStyleButtons();
}
});
styledText.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
handleTextSelected(event);
}
});
}
private ToolBar createToolBar(Composite parent) {
ToolBar toolBar = new ToolBar(parent, SWT.FLAT);
boldBtn = new ToolItem(toolBar, SWT.CHECK);
boldBtn.setImage(RichTextImages.IMG_BOLD);
boldBtn.setToolTipText(RichTextStrings.boldBtn_tooltipText);
boldBtn.addSelectionListener(
new FontStyleButtonListener(FontStyle.BOLD));
italicBtn = new ToolItem(toolBar, SWT.CHECK);
italicBtn.setImage(RichTextImages.IMG_ITALIC);
italicBtn.setToolTipText(RichTextStrings.italicBtn_tooltipText);
italicBtn.addSelectionListener(
new FontStyleButtonListener(FontStyle.ITALIC));
underlineBtn = new ToolItem(toolBar, SWT.CHECK);
underlineBtn.setImage(RichTextImages.IMG_UNDERLINE);
underlineBtn.setToolTipText(RichTextStrings.underlineBtn_tooltipText);
underlineBtn.addSelectionListener(
new FontStyleButtonListener(FontStyle.UNDERLINE));
strikeThroughBtn = new ToolItem(toolBar, SWT.CHECK);
strikeThroughBtn.setImage(RichTextImages.IMG_STRIKE_THROUGH);
strikeThroughBtn.setToolTipText(RichTextStrings.strikeThroughBtn_tooltipText);
strikeThroughBtn.addSelectionListener(
new FontStyleButtonListener(FontStyle.STRIKE_THROUGH));
new ToolItem(toolBar, SWT.SEPARATOR);
ToolItem cutBtn = new ToolItem(toolBar, SWT.PUSH);
cutBtn.setImage(RichTextImages.IMG_CUT);
cutBtn.setToolTipText(RichTextStrings.cutBtn_tooltipText);
cutBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
handleCutCopy();
styledText.cut();
}
});
ToolItem copyBtn = new ToolItem(toolBar, SWT.PUSH);
copyBtn.setImage(RichTextImages.IMG_COPY);
copyBtn.setToolTipText(RichTextStrings.copyBtn_tooltipText);
copyBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
handleCutCopy();
styledText.copy();
}
});
pasteBtn = new ToolItem(toolBar, SWT.PUSH);
pasteBtn.setEnabled(false);
pasteBtn.setImage(RichTextImages.IMG_PASTE);
pasteBtn.setToolTipText(RichTextStrings.pasteBtn_tooltipText);
pasteBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
styledText.paste();
}
});
new ToolItem(toolBar, SWT.SEPARATOR);
eraserBtn = new ToolItem(toolBar, SWT.PUSH);
eraserBtn.setEnabled(false);
eraserBtn.setImage(RichTextImages.IMG_ERASER);
eraserBtn.setToolTipText(RichTextStrings.eraserBtn_tooltipText);
eraserBtn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
clearStylesFromSelection();
}
});
return toolBar;
}
private List translateStyle(StyleRange range) {
List list = new ArrayList();
if ((range.fontStyle & SWT.BOLD) != 0) {
list.add(FontStyle.BOLD);
}
if ((range.fontStyle & SWT.ITALIC) != 0) {
list.add(FontStyle.ITALIC);
}
if (range.strikeout) {
list.add(FontStyle.STRIKE_THROUGH);
}
if (range.underline) {
list.add(FontStyle.UNDERLINE);
}
return list;
}
private class FontStyleButtonListener extends SelectionAdapter {
private FontStyle style;
public FontStyleButtonListener(FontStyle style) {
this.style = style;
}
@Override
public void widgetSelected(SelectionEvent e) {
applyFontStyleToSelection(style);
}
}
}
public enum FontStyle {
BOLD, ITALIC, STRIKE_THROUGH, UNDERLINE
}
下一个称为RichStringBuilder,它将用作帮助程序类,以将StyledText组件的内容格式化为基本HTML:
import java.util.Stack;
public final class RichStringBuilder {
public static final String LINE_DELIMITER = "<br/>";
private StringBuilder builder;
private Stack fontStyleStack;
public RichStringBuilder() {
builder = new StringBuilder();
fontStyleStack = new Stack();
}
public RichStringBuilder append(String text) {
builder.append(text);
return this;
}
public RichStringBuilder appendLineBreak() {
builder.append(LINE_DELIMITER);
return this;
}
public RichStringBuilder startParagraph() {
builder.append("<p>");
return this;
}
public RichStringBuilder startFontStyle(FontStyle fontStyle) {
fontStyleStack.push(fontStyle);
internalStartFontStyle(fontStyle);
return this;
}
public RichStringBuilder startFontStyles(FontStyle... fontStyles) {
for (FontStyle fs : fontStyles) {
startFontStyle(fs);
}
return this;
}
public RichStringBuilder endFontStyles(int count) {
for (int i = 0;i < count;i++) {
endStyle();
}
return this;
}
public RichStringBuilder endStyle() {
if (fontStyleStack.size() > 0) {
FontStyle style = fontStyleStack.pop();
internalEndFontStyle(style);
}
return this;
}
public RichStringBuilder endParagraph() {
flushStyles();
builder.append("</p>");
return this;
}
public void flushStyles() {
while (fontStyleStack.size() > 0) {
endStyle();
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (null == o) return false;
if (!(o instanceof RichStringBuilder)) return false;
return ((RichStringBuilder) o).builder.equals(builder);
}
@Override
public int hashCode() {
return builder.hashCode();
}
@Override
public String toString() {
return builder.toString();
}
private void internalStartFontStyle(FontStyle fontStyle) {
switch (fontStyle) {
case BOLD:
builder.append("<b>");
break;
case ITALIC:
builder.append("<i>");
break;
case STRIKE_THROUGH:
builder.append("<del>");
break;
case UNDERLINE:
builder.append("<ins>");
break;
}
}
private void internalEndFontStyle(FontStyle fontStyle) {
switch (fontStyle) {
case BOLD:
builder.append("</b>");
break;
case ITALIC:
builder.append("</i>");
break;
case STRIKE_THROUGH:
builder.append("</del>");
break;
case UNDERLINE:
builder.append("</ins>");
break;
}
}
}
第三个是基于SAX的内容处理程序,它将基本HTML解析为StyledText控件时将启动事件:
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public final class RichTextParser {
public static RichTextParser parse(String formattedText)
throws ParserConfigurationException, SAXException, IOException {
return new RichTextParser(formattedText);
}
private StringBuilder text = new StringBuilder();
private List styleRanges = new ArrayList();
private RichTextParser(String formattedText)
throws ParserConfigurationException, SAXException, IOException {
StringReader reader = new StringReader(formattedText);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
DefaultHandler handler = new RichTextContentHandler();
parser.parse(new InputSource(reader), handler);
}
public String getText() {
return text.toString();
}
public StyleRange[] getStyleRanges() {
return styleRanges.toArray(new StyleRange[styleRanges.size()]);
}
private class RichTextContentHandler extends DefaultHandler {
private Stack<List> stylesStack = new Stack<List>();
private String lastTextChunk = null;
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
lastTextChunk = new String(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// If there is not any previous text chunk parsed then return
if (lastTextChunk == null) return;
// If the tag found is not a supported one then return
if (!"p".equals(qName) || !"b".equals(qName) || !"i".equals(qName) ||
!"ins".equals(qName) || !"del".equals(qName)) {
return;
}
List lastStyles = lastFontStyles(true);
if (lastStyles != null) {
StyleRange range = transform(lastStyles);
range.start = currentIndex() + 1;
range.length = lastTextChunk.length();
styleRanges.add(range);
}
text.append(lastTextChunk);
lastTextChunk = null;
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
// If the tag found is not a supported one then return
if (!"p".equals(qName) || !"b".equals(qName) || !"i".equals(qName) ||
!"ins".equals(qName) || !"del".equals(qName)) {
return;
}
List lastStyles = lastFontStyles(false);
if (lastTextChunk == null) {
if (lastStyles == null) {
lastStyles = new ArrayList();
stylesStack.add(lastStyles);
}
} else {
if (lastStyles != null) {
StyleRange range = transform(lastStyles);
range.start = currentIndex() + 1;
range.length = lastTextChunk.length();
styleRanges.add(range);
}
text.append(lastTextChunk);
lastTextChunk = null;
}
if ("b".equals(qName)) {
lastStyles.add(FontStyle.BOLD);
} else if ("i".equals(qName)) {
lastStyles.add(FontStyle.ITALIC);
} else if ("ins".equals(qName)) {
lastStyles.add(FontStyle.UNDERLINE);
} else {
lastStyles.add(FontStyle.STRIKE_THROUGH);
}
}
private StyleRange transform(List styles) {
StyleRange range = new StyleRange();
range.start = currentIndex() + 1;
range.length = lastTextChunk.length();
for (FontStyle fs : styles) {
if (FontStyle.BOLD == fs) {
range.fontStyle = (range.fontStyle & SWT.BOLD);
} else if (FontStyle.ITALIC == fs) {
range.fontStyle = (range.fontStyle & SWT.ITALIC);
} else if (FontStyle.STRIKE_THROUGH == fs) {
range.strikeout = true;
} else if (FontStyle.UNDERLINE == fs) {
range.underline = true;
}
}
return range;
}
private List lastFontStyles(boolean remove) {
List lastStyles = null;
if (stylesStack.size() > 0) {
if (remove) {
lastStyles = stylesStack.pop();
} else {
lastStyles = stylesStack.peek();
}
}
return lastStyles;
}
private int currentIndex() {
return text.length() - 1;
}
}
}
翻译自: https://www.javacodegeeks.com/2012/07/richtext-editor-component-for-swt-based.html
小程序richtext