/******************************************************************************* * Copyright (c) 2011 Subgraph. * 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: * Subgraph - initial API and implementation ******************************************************************************/ package com.subgraph.vega.ui.httpviewer; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IPaintPositionManager; import org.eclipse.jface.text.IPainter; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextInputListener; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextViewer; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.StyledTextContent; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.graphics.GlyphMetrics; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; public class EmbeddedControlPainter implements IPainter, PaintListener, ControlListener, VerifyListener, ITextInputListener { /* zero width no-break space */ private final static char TAG_CHARACTER = '\uFEFF'; private final static int ARBITRARY_TAG_CHARACTER_WIDTH = 10; private final static int HORIZONTAL_MARGIN = 2; private final static int VERTICAL_MARGIN = 8; private final TextViewer textViewer; private final StyledText textWidget; private final Control embeddedControl; private final int minControlHeight; private final Position controlPosition; private IPaintPositionManager paintPositionManager; private boolean isActive = false; public EmbeddedControlPainter(TextViewer textViewer, Control embeddedControl, int minControlHeight) { this.textViewer = textViewer; this.textWidget = textViewer.getTextWidget(); this.embeddedControl = embeddedControl; this.minControlHeight = minControlHeight; this.controlPosition = new Position(0, 0); appendTagToDocument(textViewer.getDocument()); textViewer.addTextInputListener(this); textWidget.addVerifyListener(this); } /** * Extract the text from a document as a String, stripping off the embedded control * tag if present. * * @param document The document to extract the content from. * @return The document content as a String, not including the tag string added by this class. */ static String getDocumentContent(IDocument document) { final String content = document.get(); final String tag = "\n" + TAG_CHARACTER; final int tagIndex = content.indexOf(tag); if(tagIndex == -1) { return content.substring(0, tagIndex); } else { return content; } } private void appendTagToDocument(IDocument document) { if(document == null) { return; } final int length = document.getLength(); final String append = "\n" + TAG_CHARACTER; try { document.replace(length, 0, append); styleTagCharacter(length + 1); } catch (BadLocationException e) { // Checked exception? nonsense. throw new IllegalStateException("Bad location?", e); } } private void styleTagCharacter(int offset) { final IRegion region = textViewer.modelRange2WidgetRange(new Region(offset, 1)); if(region != null) { final StyleRange style = new StyleRange(); style.start = region.getOffset(); style.length = region.getLength(); style.metrics = new GlyphMetrics(minControlHeight, (2 * VERTICAL_MARGIN), ARBITRARY_TAG_CHARACTER_WIDTH); textWidget.setStyleRange(style); } } @Override public void dispose() { } @Override public void paint(int reason) { final IDocument document = textViewer.getDocument(); if(document == null) { deactivate(false); return; } if(!isActive) { textViewer.getTextWidget().addPaintListener(this); textViewer.getTextWidget().addControlListener(this); paintPositionManager.managePosition(controlPosition); isActive = true; findControlTag(); return; } if(reason == TEXT_CHANGE || reason == KEY_STROKE || reason == INTERNAL || reason == CONFIGURATION) findControlTag(); } private boolean findControlTag() { final StyledTextContent content = textWidget.getContent(); final int count = content.getCharCount(); final String text = content.getTextRange(0, count); final int idx = text.indexOf(TAG_CHARACTER); if(idx == -1) { controlPosition.setOffset(0); controlPosition.setLength(0); return false; } controlPosition.setOffset(idx); controlPosition.setLength(1); controlPosition.isDeleted = false; return true; } @Override public void deactivate(boolean redraw) { if(isActive) { isActive = false; textViewer.getTextWidget().removePaintListener(this); textViewer.getTextWidget().removeControlListener(this); if(paintPositionManager != null) paintPositionManager.unmanagePosition(controlPosition); if(redraw) redrawControlPosition(); } } @Override public void setPositionManager(IPaintPositionManager manager) { this.paintPositionManager = manager; } @Override public void paintControl(PaintEvent e) { if(textWidget != null) drawControl(); } private int getWidgetOffsetForControl() { if(controlPosition.isDeleted || controlPosition.getLength() < 1) return -1; final int controlOffset = controlPosition.getOffset(); final IRegion visibleRegion = textViewer.getVisibleRegion(); if(visibleRegion.getOffset() > controlOffset || visibleRegion.getOffset() + visibleRegion.getLength() < controlOffset) return -1; return controlOffset - visibleRegion.getOffset(); } private void drawControl() { final int offset = getWidgetOffsetForControl(); if(offset > 0) { final Point location = textWidget.getLocationAtOffset(offset); Rectangle r = textWidget.getClientArea(); int height = Math.max(minControlHeight, r.height - location.y - (VERTICAL_MARGIN * 2)); embeddedControl.setBounds( location.x + HORIZONTAL_MARGIN, location.y + VERTICAL_MARGIN, r.width - (HORIZONTAL_MARGIN * 2), height); } } private void redrawControlPosition() { final int offset = getWidgetOffsetForControl(); final int length = controlPosition.getLength(); if(offset > 0 && length > 0) { textWidget.redrawRange(offset, length, true); } } @Override public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { } @Override public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { appendTagToDocument(newInput); drawControl(); } @Override public void controlMoved(ControlEvent e) { } @Override public void controlResized(ControlEvent e) { if(textWidget != null) drawControl(); } @Override public void verifyText(VerifyEvent e) { final int count = textWidget.getCharCount(); /* Don't allow any changes at the end of the document */ if(count - e.end < 4) { e.doit = false; return; } final int length = e.end - e.start; if(length > 0) { /* Probably redundant, but definitely don't allow removing tag character */ final String oldText = (textWidget.getText(e.start, e.end - 1)); if(oldText.indexOf(TAG_CHARACTER) != -1) { e.doit = false; } } } }