package org.eclipse.swt.custom; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GlyphMetrics; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import com.yoursway.swt.additions.YsSwtGeometry; import com.yoursway.swt.styledtext.extended.Inset; import com.yoursway.swt.styledtext.extended.InsetSite; import com.yoursway.swt.styledtext.extended.ResizeListener; import com.yoursway.swt.styledtext.extended.internal.InsetPlace; import com.yoursway.utils.annotations.Reentrant_CallFromAnyThread; import com.yoursway.utils.annotations.UseFromUIThread; @UseFromUIThread public class YourSwayStyledTextInternal extends StyledText { private final Collection<InsetPlace> insetPlaces = new LinkedList<InsetPlace>(); private final Collection<ResizeListener> resizeListeners = new LinkedList<ResizeListener>(); public YourSwayStyledTextInternal(Composite parent, int style) { super(parent, style); addVerifyListener(new VerifyListener() { public void verifyText(VerifyEvent e) { int start = e.start; int replaceCharCount = e.end - e.start; int newCharCount = e.text.length(); Iterator<InsetPlace> it = insetPlaces.iterator(); while (it.hasNext()) { InsetPlace insetPlace = it.next(); int offset = insetPlace.offset(); if (start <= offset && offset < start + replaceCharCount) { insetPlace.dispose(); it.remove(); } else if (offset >= start) { offset += newCharCount - replaceCharCount; insetPlace.offset(offset); } } } }); addPaintObjectListener(new PaintObjectListener() { public void paintObject(PaintObjectEvent e) { int offset = e.style.start; for (InsetPlace insetPlace : insetPlaces) { if (offset == insetPlace.offset()) { insetPlace.updateLocation(); break; } } } }); addControlListener(new ControlListener() { public void controlMoved(ControlEvent e) { // nothing } public void controlResized(ControlEvent e) { for (ResizeListener listener : resizeListeners) { listener.resized(getSize()); } redraw(); //? } }); ExtendedTextController controller = new ExtendedTextController(this); addVerifyKeyListener(controller); addKeyListener(controller); addMouseListener(controller); } @Override public void scroll(int destX, int destY, int x, int y, int width, int height, boolean all) { super.scroll(destX, destY, x, y, width, height, false); } @Override boolean scrollVertical(int pixels, boolean adjustScrollBar) { boolean scrolled = super.scrollVertical(pixels, adjustScrollBar); for (InsetPlace insetPlace : insetPlaces) { Point loc = insetPlace.getLocation(); insetPlace.setLocation(loc.x, loc.y - pixels); } return scrolled; } @Override boolean scrollHorizontal(int pixels, boolean adjustScrollBar) { boolean scrolled = super.scrollHorizontal(pixels, adjustScrollBar); for (InsetPlace insetPlace : insetPlaces) { Point loc = insetPlace.getLocation(); insetPlace.setLocation(loc.x - pixels, loc.y); } return scrolled; } @Override void scrollText(int srcY, int destY) { super.scrollText(srcY, destY); for (InsetPlace insetPlace : insetPlaces) { Point loc = insetPlace.getLocation(); if (loc.y >= srcY) insetPlace.setLocation(loc.x, loc.y + destY - srcY); } } @Reentrant_CallFromAnyThread public String insetPlaceholder() { return "\uFFFC"; } @Reentrant_CallFromAnyThread private int insetPlaceholderLength() throws AssertionError { if (insetPlaceholder().length() != 1) throw new AssertionError("An inset placeholder must have 1 char length."); return 1; } public void addInset(int lineIndex, final Inset inset) { int offset = lineEndOffset(lineIndex); replaceTextRange(offset, 0, "\n" + insetPlaceholder()); offset++; // "\n" final Composite composite = new Composite(this, SWT.NO_FOCUS | SWT.NO_BACKGROUND); final InsetPlace insetPlace = new InsetPlace(inset, offset, composite, this); insetPlaces.add(insetPlace); composite.addControlListener(new ControlListener() { public void controlMoved(ControlEvent e) { // nothing } public void controlResized(ControlEvent e) { updateMetrics(insetPlace.offset(), composite.getSize()); } }); inset.init(composite, new InsetSite() { public Color getBackground() { return YourSwayStyledTextInternal.this.getBackground(); } public Point clientAreaSize() { return YsSwtGeometry.size(getClientArea()); } public void addResizeListener(ResizeListener listener) { resizeListeners.add(listener); } }); } public int lineEndOffset(int lineIndex) { int lineOffset = getOffsetAtLine(lineIndex); int lineLength = getLine(lineIndex).length(); return lineOffset + lineLength; } public Inset existingInset(int lineIndex) { int offset = lineEndOffset(lineIndex) + 1; for (InsetPlace insetPlace : insetPlaces) { if (insetPlace.offset() == offset) return insetPlace.inset(); } return null; } public boolean removeInset(int lineIndex) { if (!lineHasInset(lineIndex)) return false; int s = insetPlaces.size(); int offset = lineEndOffset(lineIndex); //! must be 2 == ("\n" + insertionPlaceholder()).length() replaceTextRange(offset, 2, ""); if (insetPlaces.size() != s - 1) throw new AssertionError("Inset object hasn't been removed from collection."); return true; } boolean lineHasInset() { return lineHasInset(selectedLines().y); } public boolean lineHasInset(int lineIndex) { return isInsetLine(lineIndex + 1); } //! public boolean isInsetLine(int lineIndex) { if (getLineCount() <= lineIndex) return false; return (getLine(lineIndex).equals(insetPlaceholder())); } private void updateMetrics(int offset, Point size) { StyleRange style = new StyleRange(); style.start = offset; style.length = insetPlaceholderLength(); int width = size.x - (size.x > 20 ? 20 : 0); // hack style.metrics = new GlyphMetrics(size.y, 0, width); setStyleRange(style); if (getCaretOffset() == offset + 2) //! magic showSelection(); } void selectInsetLineEnd() { if (!lineHasInset()) throw new AssertionError("Selected line must have an inset."); int offset = lineEndOffset(selectedLines().y + 1); setSelection(offset); } //? public Point selectedLines() { Point sel = getSelection(); int firstLine = getLineAtOffset(sel.x); int lastLine = getLineAtOffset(sel.y); if (firstLine > lastLine) throw new AssertionError("First line of selection must be <= than last."); return new Point(firstLine, lastLine); } public boolean inInsetLine() { return isInsetLine(caretLine()); } void moveCaretFromInsetLine(boolean selection) { if (inInsetLine()) moveCaret(selection, atLineBegin() ? -1 : inLastLine() ? -2 : 1); } //? public int caretLine() { return getLineAtOffset(getCaretOffset()); } private void moveCaret(boolean selection, int where) { if (selection) { Point sel = getSelection(); if (caretAtSelectionEnd()) setSelection(sel.x, sel.y + where); else setSelection(sel.x + where, sel.y); } else { setCaretOffset(getCaretOffset() + where); } } private boolean caretAtSelectionEnd() { return getCaretOffset() == getSelection().y; } boolean atLineBegin() { int offset = getOffsetAtLine(caretLine()); return getCaretOffset() == offset; } boolean inLastLine() { return caretLine() == getLineCount() - 1; } @Override void doLineDown(boolean select) { // TODO Auto-generated method stub super.doLineDown(select); } @Override void doLineUp(boolean select) { // TODO Auto-generated method stub super.doLineUp(select); } }