268 lines
8.9 KiB
Java
268 lines
8.9 KiB
Java
|
|
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;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* <pre>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?)</pre>
|
||
|
|
*/
|
||
|
|
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//
|