package org.eclipse.swt.graphics; import org.eclipse.swt.*; import org.eclipse.swt.custom.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; import com.common.util.LiteHashMap; import com.common.util.LiteList; import com.common.util.optimized.IntObjectHashMap; /** *
Attempt at writing an SWT document display that allows:
 * word wrap or no word wrap
 * document within document
 * images with text wrap & attached to top, bottom, or text position (center, left, right align?)
 * hyperlinks
 * other controls (maybe just panel?)
*/ public class StyledDocument extends Canvas { public static final int LEFT_WRAPPED = 0; public static final int RIGHT_WRAPPED = 1; public static final int UNWRAPPED = 2; private int width = 0; private String text = null; private String[] paragraphs = null; /** Assumes currently that the images are ordered first by paragraph #, then by VALIGN (TOP, then BOTTOM), then by HALIGN (HALIGN_LEFT, HALIGN_CENTER, HALIGN_RIGHT, HALIGN_LEFT_WRAPPED, then HALIGN_RIGHT_WRAPPED). */ private ParagraphContainer[] paragraphContainers = null; /** Whether the metadata on the content has been reset, requiring a re-layout of the text. */ private boolean isReset = true; private static class DocumentPart { public TextLayout partLayout = null; public int lineCount = 0; }//DocumentPart// private static class ComponentPart { public Image image = null; public Control control = null; /** The orientation of the component part, either LEFT_WRAPPED, RIGHT_WRAPPED, or UNWRAPPED (if it is the top component). */ public int orientation = LEFT_WRAPPED; public int leftPadding = 0; public int rightPadding = 0; public int topPadding = 0; public int bottomPadding = 0; /** Only used with wrapped parts. Determines how far from the preceeding component part or paragraph top before text starts wrapping and the component appears. */ public int offset = 0; }//ComponentPart// private static class ParagraphContainer { /** A collection of TextLayout instances used in a previous rendering. */ public LiteList layouts = new LiteList(1, 10); public ComponentPart top = null; public LiteList left = null; public LiteList right = null; public void reset() { for(int index = layouts.getSize() - 1; index >= 0; index--) { ((TextLayout) layouts.remove(index)).dispose(); }//for// }//reset()// }//ParagraphContainer// /** * StyledDocument constructor. * @param parent * @param style */ public StyledDocument(Composite parent, int style) { super(parent, style); addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { organize(); } }); addControlListener(new ControlListener() { public void controlResized(ControlEvent e) { width = getSize().x; reset(); } public void controlMoved(ControlEvent e) { } }); }//StyledDocument()// /** * */ private void organize() { //If the control has been reset then re-layout the text.// if(isReset) { //Iterate over the paragraphs, processing them one at a time.// for(int paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) { String subText = paragraphs[paragraphIndex]; ParagraphContainer container = paragraphContainers[paragraphIndex]; if(container.left == null && container.right == null) { TextLayout textLayout = new TextLayout(getDisplay()); textLayout.setText(subText); container.layouts.add(textLayout); }//if// else { ComponentPart nextLeft = null; ComponentPart nextRight = null; while(subText != null) { TextLayout textLayout = new TextLayout(getDisplay()); int lineIndex = 0; int height = 0; Rectangle lineBounds; textLayout.setText(subText); textLayout.setWidth(width); lineBounds = textLayout.getLineBounds(lineIndex); height += lineBounds.height; while(height) textLayout.draw(e.gc, 0, 0); }//while// }//else// }//for// isReset = false; }//if// }//organize()// /** * Adds a control to the document. * @param paragraphIndex The index of the paragraph the control is attached to. * @param orientation The orientation of the control relative to the paragraph. * @param control The control being added. * @param topPadding The padding above the control that text will not exist in. * @param rightPadding The padding right of the control that text will not exist in. * @param bottomPadding The padding below the control that text will not exist in. * @param leftPadding The padding left of the control that text will not exist in. * @param offset The offset between the control and the previous control/image or top of the paragraph that text can exist within. This does not include padding, and is ignored if the orientation is UNWRAPPED. */ public void addControl(int paragraphIndex, int orientation, Control control, int topPadding, int rightPadding, int bottomPadding, int leftPadding, int offset) { add(paragraphIndex, orientation, control, topPadding, rightPadding, bottomPadding, leftPadding, offset); }//addControl()// /** * Adds an image to the document. * @param paragraphIndex The index of the paragraph the image is attached to. * @param orientation The orientation of the image relative to the paragraph. * @param control The image being added. * @param topPadding The padding above the image that text will not exist in. * @param rightPadding The padding right of the image that text will not exist in. * @param bottomPadding The padding below the image that text will not exist in. * @param leftPadding The padding left of the image that text will not exist in. * @param offset The offset between the image and the previous control/image or top of the paragraph that text can exist within. This does not include padding, and is ignored if the orientation is UNWRAPPED. */ public void addImage(int paragraphIndex, int orientation, Image image, int topPadding, int rightPadding, int bottomPadding, int leftPadding, int offset) { add(paragraphIndex, orientation, image, topPadding, rightPadding, bottomPadding, leftPadding, offset); }//addImage()// private void add(int paragraphIndex, int orientation, Object controlOrImage, int topPadding, int rightPadding, int bottomPadding, int leftPadding, int offset) { if(paragraphIndex >= 0 && paragraphIndex < paragraphContainers.length) { ParagraphContainer container = paragraphContainers[paragraphIndex]; ComponentPart part = new ComponentPart(); if(controlOrImage instanceof Image) { part.image = (Image) controlOrImage; }//if// else if(controlOrImage instanceof Control) { part.control = (Control) controlOrImage; }//else if// part.topPadding = topPadding; part.rightPadding = rightPadding; part.bottomPadding = bottomPadding; part.leftPadding = leftPadding; switch(orientation) { case UNWRAPPED: { if(container.top == null) { container.top = part; }//if// else { throw new RuntimeException("Cannot have multiple top components."); }//else// break; }//case// case RIGHT_WRAPPED: { if(container.right == null) { container.right = new LiteList(3, 10); }//if// container.right.add(part); break; }//case// case LEFT_WRAPPED: { if(container.left == null) { container.left = new LiteList(3, 10); }//if// container.left.add(part); break; }//case// }//switch// }//if// else { throw new IndexOutOfBoundsException(); }//else// }//add()// /** * Sets the text, reseting the entire control. * @param text The initial text for the control. */ public void setText(String text) { text = text.replace("\r\n", "\n"); if(!text.equals(this.text)) { this.text = text; this.paragraphs = this.text.split("\n"); //TODO: Should we remove empty paragraphs? //Cleanup old metadata.// reset(); //Create a new metadata container.// this.paragraphContainers = new ParagraphContainer[paragraphs.length]; //Setup new metadata.// for(int index = 0; index < this.paragraphContainers.length; index++) { this.paragraphContainers[index] = new ParagraphContainer(); }//for// }//if// }//setText()// /** * Resets the control by clearing any cached display data. */ public void reset() { //Cleanup old metadata.// if(this.paragraphContainers != null) { for(int index = 0; index < this.paragraphContainers.length; index++) { this.paragraphContainers[index].reset(); }//for// }//if// isReset = true; }//reset()// /* (non-Javadoc) * @see org.eclipse.swt.widgets.Widget#dispose() */ public void dispose() { reset(); super.dispose(); }//dispose()// public static void main(String[] args) { final Display display = new Display(); final Shell shell = new Shell(display); StyledDocument doc = null; shell.setLayout(new FillLayout()); doc = new StyledDocument(shell, SWT.WRAP | SWT.BORDER); shell.setSize(400, 400); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } }//StyledDocument//