| /******************************************************************************* |
| * Copyright (c) 2000, 2016 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 |
| *******************************************************************************/ |
| package org.eclipse.help.ui.internal; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyleRange; |
| import org.eclipse.swt.custom.StyledTextContent; |
| import org.eclipse.swt.custom.TextChangeListener; |
| import org.eclipse.swt.graphics.Drawable; |
| import org.eclipse.swt.graphics.GC; |
| |
| import com.ibm.icu.text.BreakIterator; |
| |
| public class StyledLineWrapper implements StyledTextContent { |
| |
| /** |
| * Internal representation of <b> - unlikely to occur in a text |
| */ |
| public static final String BOLD_CLOSE_TAG = "</@#$b>"; //$NON-NLS-1$ |
| /** |
| * Internal representation of <b> - unlikely to occur in a text |
| */ |
| public static final String BOLD_TAG = "<@#$b>"; //$NON-NLS-1$ |
| |
| private Drawable drawable; |
| |
| /** Lines after splitting */ |
| private ArrayList<String> lines = new ArrayList<>(); |
| |
| /** Style ranges, per line */ |
| private ArrayList<StyleRange> lineStyleRanges = new ArrayList<>(); |
| |
| /** Character count */ |
| private int charCount = -1; |
| |
| /** Line breaker */ |
| private static BreakIterator lineBreaker = BreakIterator.getLineInstance(); |
| |
| /** Beyond this length (pixels), lines should wrap */ |
| public final static int DEFAULT_WIDTH = 350; |
| |
| public int maxWidth; |
| |
| /** |
| * Constructor |
| */ |
| public StyledLineWrapper(String text, Drawable drawable, int minWidth) { |
| this.drawable = drawable; |
| maxWidth = Math.max(DEFAULT_WIDTH, minWidth); |
| if (text == null || text.length() == 0) |
| text = " "; // use one blank space //$NON-NLS-1$ |
| setText(text); |
| } |
| |
| /** |
| * @see StyledTextContent#addTextChangeListener(TextChangeListener) |
| */ |
| @Override |
| public void addTextChangeListener(TextChangeListener l) { |
| // do nothing |
| } |
| |
| /** |
| * @see StyledTextContent#getCharCount() |
| */ |
| @Override |
| public int getCharCount() { |
| if (charCount != -1) |
| return charCount; |
| charCount = 0; |
| for (Iterator<String> i = lines.iterator(); i.hasNext();) |
| charCount += i.next().length(); |
| return charCount; |
| } |
| |
| /** |
| * @see StyledTextContent#getLine(int) |
| */ |
| @Override |
| public String getLine(int i) { |
| if ((i >= lines.size()) || (i < 0)) |
| SWT.error(SWT.ERROR_INVALID_ARGUMENT); |
| return lines.get(i); |
| } |
| |
| /** |
| * @see StyledTextContent#getLineAtOffset(int) |
| */ |
| @Override |
| public int getLineAtOffset(int offset) { |
| if (offset >= getCharCount()) |
| return getLineCount() - 1; |
| int count = 0; |
| int line = -1; |
| while (count <= offset) { |
| count += getLine(++line).length(); |
| } |
| return line; |
| } |
| |
| /** |
| * @see StyledTextContent#getLineCount() |
| */ |
| @Override |
| public int getLineCount() { |
| if (lines.size() == 0) |
| return 1; |
| return lines.size(); |
| } |
| |
| /** |
| * @see StyledTextContent#getLineDelimiter() |
| */ |
| @Override |
| public String getLineDelimiter() { |
| return null; |
| } |
| |
| /** |
| * @see StyledTextContent#getOffsetAtLine(int) |
| */ |
| @Override |
| public int getOffsetAtLine(int line) { |
| if (lines.size() == 0) |
| return 0; |
| int offset = 0; |
| for (int i = 0; i < line; i++) |
| offset += getLine(i).length(); |
| return offset; |
| } |
| |
| /** |
| * @see StyledTextContent#getTextRange(int, int) |
| */ |
| @Override |
| public String getTextRange(int start, int end) { |
| int l1 = getLineAtOffset(start); |
| int l2 = getLineAtOffset(end); |
| if (l1 == l2) |
| return getLine(l1).substring(start - getOffsetAtLine(l1), |
| end - start); |
| StringBuffer range = new StringBuffer(getLine(l1).substring( |
| start - getOffsetAtLine(l1))); |
| for (int i = l1 + 1; i < l2; i++) |
| range.append(getLine(i)); |
| range.append(getLine(l2).substring(0, end - getOffsetAtLine(l2))); |
| return range.toString(); |
| } |
| |
| /** |
| * @see StyledTextContent#removeTextChangeListener(TextChangeListener) |
| */ |
| @Override |
| public void removeTextChangeListener(TextChangeListener arg0) { |
| // do nothing |
| } |
| |
| /** |
| * @see StyledTextContent#replaceTextRange(int, int, String) |
| */ |
| @Override |
| public void replaceTextRange(int arg0, int arg1, String arg2) { |
| // do nothing |
| } |
| |
| /** |
| * @see StyledTextContent#setText(String) |
| */ |
| @Override |
| public void setText(String text) { |
| if (text == null) |
| text = " "; //$NON-NLS-1$ |
| processLineBreaks(text); |
| processStyles(text); |
| } |
| |
| /** |
| * Returns the array of styles. |
| */ |
| public StyleRange[] getStyles() { |
| StyleRange[] array = new StyleRange[lineStyleRanges.size()]; |
| lineStyleRanges.toArray(array); |
| return array; |
| } |
| |
| /** |
| * Create an array of lines with sytles stripped off. Each lines is at most |
| * MAX_LINE_LENGTH characters. |
| */ |
| private void processLineBreaks(String text) { |
| // Create the original lines with style stripped |
| lines = new ArrayList<>(); |
| char[] textChars = getUnstyledText(text).toCharArray(); |
| int start = 0; |
| for (int i = start; i < textChars.length; i++) { |
| char ch = textChars[i]; |
| if (ch == SWT.CR) { |
| lines.add(new String(textChars, start, i - start)); |
| start = i + 1; |
| // if we reached the end, stop |
| if (start >= textChars.length) |
| break; |
| { // see if the next character is an LF |
| ch = textChars[start]; |
| if (ch == SWT.LF) { |
| start++; |
| i++; |
| if (start >= textChars.length) |
| break; |
| } |
| } |
| } else if (ch == SWT.LF) { |
| lines.add(new String(textChars, start, i - start)); |
| start = i + 1; |
| if (start >= textChars.length) |
| break; |
| } else if (i == textChars.length - 1) { |
| lines.add(new String(textChars, start, i - start + 1)); |
| } |
| } |
| // Break long lines |
| GC gc = new GC(drawable); |
| for (int i = 0; i < lines.size(); i++) { |
| String line = lines.get(i); |
| while (line.length() > 0) { |
| int linebreak = getLineBreak(line, gc); |
| if (linebreak == 0 || linebreak == line.length()) |
| break; |
| String newline = line.substring(0, linebreak); |
| lines.remove(i); |
| lines.add(i, newline); |
| line = line.substring(linebreak); |
| lines.add(++i, line); |
| } |
| } |
| gc.dispose(); |
| } |
| |
| /** |
| * Returns the text without the style |
| */ |
| private static String getUnstyledText(String styledText) { |
| return styledText.replaceAll("</?@#\\$b>", ""); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| /** |
| * Finds a good line breaking point |
| */ |
| private int getLineBreak(String line, GC gc) { |
| lineBreaker.setText(line); |
| int lastGoodIndex = 0; |
| int currentIndex = lineBreaker.first(); |
| int width = gc.textExtent(line.substring(0, currentIndex)).x; |
| while (width < maxWidth && currentIndex != BreakIterator.DONE) { |
| lastGoodIndex = currentIndex; |
| currentIndex = lineBreaker.next(); |
| if (currentIndex == BreakIterator.DONE) { |
| break; |
| } |
| width = gc.textExtent(line.substring(0, currentIndex)).x; |
| } |
| return lastGoodIndex; |
| } |
| |
| /** |
| * Creates all the (bold) style ranges for the text. It is assumed that the |
| * text has been split across lines. |
| */ |
| private void processStyles(String text) { |
| // create a new array of styles |
| lineStyleRanges = new ArrayList<>(); |
| // first, remove the line breaks |
| text = text.replaceAll("\n|\r", ""); //$NON-NLS-1$ //$NON-NLS-2$ |
| int offset = 0; |
| do { |
| // create a style |
| StyleRange style = new StyleRange(); |
| style.fontStyle = SWT.BOLD; |
| // the index of the starting style in styled text |
| int start = text.indexOf(BOLD_TAG, offset); |
| if (start == -1) |
| break; |
| String prefix = getUnstyledText(text.substring(0, start)); |
| style.start = prefix.length(); |
| // the index of the ending style in styled text |
| offset = start + 1; |
| int end = text.indexOf(BOLD_CLOSE_TAG, offset); |
| if (end == -1) |
| break; |
| prefix = getUnstyledText(text.substring(0, end)); |
| style.length = prefix.length() - style.start; |
| lineStyleRanges.add(style); |
| offset = end + 1; |
| } while (offset < text.length()); |
| } |
| } |