/* * Copyright (c) 2011, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.ui.internal.text.html; import com.ibm.icu.text.BreakIterator; import org.eclipse.jface.text.DefaultInformationControl; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextPresentation; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.Drawable; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.widgets.Display; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.Iterator; /** * <p> * Moved into this package from <code>org.eclipse.jface.internal.text.revisions</code>. * </p> */ public class HTMLTextPresenter implements DefaultInformationControl.IInformationPresenter, DefaultInformationControl.IInformationPresenterExtension { public class LineBreakingReader { private BufferedReader fReader; private GC fGC; private int fMaxWidth; private String fLine; private int fOffset; private BreakIterator fLineBreakIterator; private boolean fBreakWords; /** * Creates a reader that breaks an input text to fit in a given width. * * @param reader Reader of the input text * @param gc The graphic context that defines the currently used font sizes * @param maxLineWidth The max width (pixels) where the text has to fit in */ public LineBreakingReader(Reader reader, GC gc, int maxLineWidth) { fReader = new BufferedReader(reader); fGC = gc; fMaxWidth = maxLineWidth; fOffset = 0; fLine = null; fLineBreakIterator = BreakIterator.getLineInstance(); fBreakWords = true; } public boolean isFormattedLine() { return fLine != null; } /** * Reads the next line. The lengths of the line will not exceed the given maximum width. * * @return the next line * @throws IOException */ public String readLine() throws IOException { if (fLine == null) { String line = fReader.readLine(); if (line == null) { return null; } int lineLen = fGC.textExtent(line).x; if (lineLen < fMaxWidth) { return line; } fLine = line; fLineBreakIterator.setText(line); fOffset = 0; } int breakOffset = findNextBreakOffset(fOffset); String res; if (breakOffset != BreakIterator.DONE) { res = fLine.substring(fOffset, breakOffset); fOffset = findWordBegin(breakOffset); if (fOffset == fLine.length()) { fLine = null; } } else { res = fLine.substring(fOffset); fLine = null; } return res; } private int findNextBreakOffset(int currOffset) { int currWidth = 0; int nextOffset = fLineBreakIterator.following(currOffset); while (nextOffset != BreakIterator.DONE) { String word = fLine.substring(currOffset, nextOffset); int wordWidth = fGC.textExtent(word).x; int nextWidth = wordWidth + currWidth; if (nextWidth > fMaxWidth) { if (currWidth > 0) { return currOffset; } if (!fBreakWords) { return nextOffset; } // need to fit into fMaxWidth int length = word.length(); while (length >= 0) { length--; word = word.substring(0, length); wordWidth = fGC.textExtent(word).x; if (wordWidth + currWidth < fMaxWidth) { return currOffset + length; } } return nextOffset; } currWidth = nextWidth; currOffset = nextOffset; nextOffset = fLineBreakIterator.next(); } return nextOffset; } private int findWordBegin(int idx) { while (idx < fLine.length() && Character.isWhitespace(fLine.charAt(idx))) { idx++; } return idx; } } private static final String LINE_DELIM = System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ private int fCounter; private boolean fEnforceUpperLineLimit; public HTMLTextPresenter() { this(true); } public HTMLTextPresenter(boolean enforceUpperLineLimit) { super(); fEnforceUpperLineLimit = enforceUpperLineLimit; } /* * @see IHoverInformationPresenter#updatePresentation(Display display, String, TextPresentation, * int, int) */ @Override public String updatePresentation(Display display, String hoverInfo, TextPresentation presentation, int maxWidth, int maxHeight) { return updatePresentation((Drawable) display, hoverInfo, presentation, maxWidth, maxHeight); } /* * @see IHoverInformationPresenterExtension#updatePresentation(Drawable drawable, String, * TextPresentation, int, int) */ @Override public String updatePresentation(Drawable drawable, String hoverInfo, TextPresentation presentation, int maxWidth, int maxHeight) { if (hoverInfo == null) { return null; } GC gc = new GC(drawable); try { StringBuffer buffer = new StringBuffer(); int maxNumberOfLines = Math.round(maxHeight / gc.getFontMetrics().getHeight()); fCounter = 0; LineBreakingReader reader = new LineBreakingReader( createReader(hoverInfo, presentation), gc, maxWidth); boolean lastLineFormatted = false; String lastLineIndent = null; String line = reader.readLine(); boolean lineFormatted = reader.isFormattedLine(); boolean firstLineProcessed = false; while (line != null) { if (fEnforceUpperLineLimit && maxNumberOfLines <= 0) { break; } if (firstLineProcessed) { if (!lastLineFormatted) { append(buffer, LINE_DELIM, null); } else { append(buffer, LINE_DELIM, presentation); if (lastLineIndent != null) { append(buffer, lastLineIndent, presentation); } } } append(buffer, line, null); firstLineProcessed = true; lastLineFormatted = lineFormatted; if (!lineFormatted) { lastLineIndent = null; } else if (lastLineIndent == null) { lastLineIndent = getIndent(line); } line = reader.readLine(); lineFormatted = reader.isFormattedLine(); maxNumberOfLines--; } if (line != null) { append(buffer, LINE_DELIM, lineFormatted ? presentation : null); append(buffer, HTMLMessages.getString("HTMLTextPresenter.ellipse"), presentation); //$NON-NLS-1$ } return trim(buffer, presentation); } catch (IOException e) { // ignore TODO do something else? return null; } finally { gc.dispose(); } } protected void adaptTextPresentation(TextPresentation presentation, int offset, int insertLength) { int yoursStart = offset; int yoursEnd = offset + insertLength - 1; yoursEnd = Math.max(yoursStart, yoursEnd); @SuppressWarnings("rawtypes") Iterator e = presentation.getAllStyleRangeIterator(); while (e.hasNext()) { StyleRange range = (StyleRange) e.next(); int myStart = range.start; int myEnd = range.start + range.length - 1; myEnd = Math.max(myStart, myEnd); if (myEnd < yoursStart) { continue; } if (myStart < yoursStart) { range.length += insertLength; } else { range.start += insertLength; } } } protected Reader createReader(String hoverInfo, TextPresentation presentation) { return new HTML2TextReader(new StringReader(hoverInfo), presentation); } private void append(StringBuffer buffer, String string, TextPresentation presentation) { int length = string.length(); buffer.append(string); if (presentation != null) { adaptTextPresentation(presentation, fCounter, length); } fCounter += length; } private String getIndent(String line) { int length = line.length(); int i = 0; while (i < length && Character.isWhitespace(line.charAt(i))) { ++i; } return (i == length ? line : line.substring(0, i)) + " "; //$NON-NLS-1$ } private String trim(StringBuffer buffer, TextPresentation presentation) { int length = buffer.length(); int end = length - 1; while (end >= 0 && Character.isWhitespace(buffer.charAt(end))) { --end; } if (end == -1) { return ""; //$NON-NLS-1$ } if (end < length - 1) { buffer.delete(end + 1, length); } else { end = length; } int start = 0; while (start < end && Character.isWhitespace(buffer.charAt(start))) { ++start; } buffer.delete(0, start); presentation.setResultWindow(new Region(start, buffer.length())); return buffer.toString(); } }