org.eclipse.mylyn.internal.wikitext.ui.viewer.ImageManager.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.mylyn.internal.wikitext.ui.viewer.ImageManager.java

Source

/*******************************************************************************
 * Copyright (c) 2007, 2009 David Green and others.
 * 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:
 *     David Green - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.internal.wikitext.ui.viewer;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.AnnotationPainter;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.mylyn.internal.wikitext.ui.WikiTextUiPlugin;
import org.eclipse.mylyn.internal.wikitext.ui.util.ImageCache;
import org.eclipse.mylyn.internal.wikitext.ui.viewer.annotation.ImageAnnotation;
import org.eclipse.mylyn.internal.wikitext.ui.viewer.annotation.ImageDrawingStrategy;
import org.eclipse.mylyn.wikitext.ui.viewer.HtmlViewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;

/**
 * Manages all aspects of image download/display in an {@link HtmlViewer}. Manages the download of images for viewing in
 * an {@link HtmlViewer}, and creates appropriate space for their display. Downloads image data in a background thread,
 * instantiates the corresopnding images, and ensures that enough vertical space exists in the viewer to display the
 * images.
 * 
 * @see ImageAnnotation
 * @see ImageDrawingStrategy
 * @see ImageCache
 * @see HtmlViewer
 * @author David Green
 */
public class ImageManager
        implements ITextInputListener, DisposeListener, IDocumentListener, ISelectionChangedListener {
    private class HyperlinkMouseListener implements MouseMoveListener, MouseListener {
        @SuppressWarnings("unused")
        public void mouseEnter(MouseEvent e) {
            // ignore
        }

        @SuppressWarnings("unused")
        public void mouseExit(MouseEvent e) {
            disarm();
        }

        public void mouseMove(MouseEvent e) {
            adjust(e);
        }

        public void mouseDoubleClick(MouseEvent e) {
            // ignore
        }

        public void mouseDown(MouseEvent e) {
            // ignore
        }

        public void mouseUp(MouseEvent e) {
            clicked(e);
        }
    }

    private final HtmlViewer viewer;

    private final Display display;

    private final ImageCache imageCache;

    private final Set<ImageAnnotation> annotations = new HashSet<>();

    private ImageResolver imageResolver;

    private boolean computingChanges;

    private final AnnotationPainter painter;

    private boolean armed = false;

    private Cursor cursor;

    private Cursor previousCursor;

    public ImageManager(HtmlViewer viewer, ImageCache imageCache, AnnotationPainter painter) {
        this.viewer = viewer;
        this.painter = painter;
        display = viewer.getTextWidget().getDisplay();
        this.imageCache = imageCache;
        inspect();
        viewer.getTextWidget().addDisposeListener(this);
        viewer.addTextInputListener(this);
        if (viewer.getDocument() != null) {
            viewer.getDocument().addDocumentListener(this);
        }
        viewer.addSelectionChangedListener(this);
        viewer.addPostSelectionChangedListener(this);

        // bug 257868 support image hyperlinks
        HyperlinkMouseListener mouseListener = new HyperlinkMouseListener();
        viewer.getTextWidget().addMouseMoveListener(mouseListener);
        viewer.getTextWidget().addMouseListener(mouseListener);

    }

    private IHyperlink getHyperlink(MouseEvent e) {
        // bug 257868 support image hyperlinks
        if (annotations.isEmpty()) {
            return null;
        }
        Point point = new Point(e.x, e.y);
        for (ImageAnnotation annotation : annotations) {
            if (annotation.getHyperlnkAnnotation() == null) {
                continue;
            }
            Rectangle region = getRegion(annotation);
            if (region != null) {
                if (region.contains(point)) {
                    AnnotationHyperlinkDetector detector = (AnnotationHyperlinkDetector) viewer.getTextWidget()
                            .getData(AnnotationHyperlinkDetector.class.getName());
                    if (detector != null) {
                        IHyperlink hyperlink = detector.createHyperlink(viewer, viewer.getAnnotationModel(),
                                annotation.getHyperlnkAnnotation());
                        return hyperlink;
                    }
                }
            }
        }
        return null;
    }

    public void clicked(MouseEvent e) {
        IHyperlink hyperlink = getHyperlink(e);
        if (hyperlink != null) {
            disarm();
            hyperlink.open();
        }
    }

    /**
     * get the widget-relative region of the given annotation
     * 
     * @return the region, or null if it is unknown
     */
    private Rectangle getRegion(ImageAnnotation annotation) {
        if (annotation.getImage() == null) {
            return null;
        }
        Position position = viewer.getAnnotationModel().getPosition(annotation);
        Point locationAtOffset = viewer.getTextWidget().getLocationAtOffset(position.offset);
        Rectangle bounds = annotation.getImage().getBounds();
        Rectangle rectange = new Rectangle(locationAtOffset.x, locationAtOffset.y, bounds.width, bounds.height);
        return rectange;
    }

    void adjust(MouseEvent e) {
        IHyperlink hyperlink = getHyperlink(e);
        if (hyperlink == null) {
            disarm();
        } else {
            // always arm here even if already armed, otherwise the cursor
            // setting interacts poorly with other things that set the cursor.
            armed = true;
            Cursor currentCursor = viewer.getTextWidget().getCursor();
            if (cursor == null || currentCursor != cursor) {
                previousCursor = currentCursor;
                if (cursor == null) {
                    cursor = new Cursor(viewer.getTextWidget().getDisplay(), SWT.CURSOR_HAND);
                }
                viewer.getTextWidget().setCursor(cursor);
            }
        }
    }

    void disarm() {
        if (armed) {
            if (previousCursor != null) {
                viewer.getTextWidget().setCursor(previousCursor);
            }
            armed = false;
        }
    }

    public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
        stop();
    }

    public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
        if (oldInput != null) {
            oldInput.removeDocumentListener(this);
        }
        if (newInput != null) {
            newInput.addDocumentListener(this);
        }
        inspect();
    }

    @SuppressWarnings("unchecked")
    private void inspect() {
        synchronized (this) {
            annotations.clear();
            if (viewer.getAnnotationModel() != null) {
                Iterator<Annotation> iterator = viewer.getAnnotationModel().getAnnotationIterator();
                while (iterator.hasNext()) {
                    Annotation annotation = iterator.next();
                    if (annotation instanceof ImageAnnotation) {
                        annotations.add((ImageAnnotation) annotation);
                    }
                }
            }
        }
        if (!annotations.isEmpty()) {
            ImageResolver resolver;
            synchronized (this) {
                resolver = imageResolver;
            }
            if (resolver != null) {
                try {
                    resolver.join();
                } catch (InterruptedException e) {
                    return;
                }
            }
            imageResolver = new ImageResolver();
            imageResolver.start();
        }
    }

    private synchronized void stop() {
        ImageResolver resolver = imageResolver;
        if (resolver != null) {
            resolver.interrupt();
        }
    }

    public void widgetDisposed(DisposeEvent e) {
        if (cursor != null) {
            cursor.dispose();
            cursor = null;
        }
        stop();
    }

    @SuppressWarnings("unchecked")
    private void updateImage(String imgSrc, ImageData imageData) {
        if (display.isDisposed() || viewer.getTextWidget().isDisposed()) {
            return;
        }
        Image image = imageData == null ? imageCache.getMissingImage()
                : ImageDescriptor.createFromImageData(imageData).createImage();
        imageCache.putImage(imgSrc, image);

        Set<ImageAnnotation> modifiedAnnotations = new HashSet<>();

        AnnotationModel annotationModel = (AnnotationModel) viewer.getAnnotationModel();
        Object annotationLockObject = annotationModel.getLockObject();
        if (annotationLockObject == null) {
            annotationLockObject = annotationModel;
        }
        synchronized (annotationLockObject) {
            Iterator<Annotation> iterator = annotationModel.getAnnotationIterator();
            while (iterator.hasNext()) {
                Annotation annotation = iterator.next();
                if (annotation instanceof ImageAnnotation) {
                    ImageAnnotation imageAnnotation = (ImageAnnotation) annotation;
                    if (imgSrc.equals(imageAnnotation.getUrl())) {
                        imageAnnotation.setImage(image);
                        modifiedAnnotations.add(imageAnnotation);
                    }
                }
            }
        }

        if (!modifiedAnnotations.isEmpty()) {
            computingChanges = true;
            try {
                boolean rangesAdjusted = false;
                List<StyleRange> ranges = new ArrayList<>();

                Iterator<?> allStyleRangeIterator = viewer.getTextPresentation().getAllStyleRangeIterator();
                while (allStyleRangeIterator.hasNext()) {
                    StyleRange range = (StyleRange) allStyleRangeIterator.next();
                    ranges.add((StyleRange) range.clone());
                }

                GC gc = new GC(viewer.getTextWidget());
                try {
                    viewer.getTextWidget().setRedraw(false);
                    TextPresentation textPresentation = viewer.getTextPresentation();
                    //         textPresentation.
                    for (ImageAnnotation annotation : modifiedAnnotations) {
                        int height = annotation.getImage().getBounds().height;
                        Position position = annotationModel.getPosition(annotation);
                        String widgetText = viewer.getTextWidget().getText();
                        Font font = null;
                        if (widgetText.length() > 0 && widgetText.length() > position.offset) {
                            StyleRange styleRange = viewer.getTextWidget().getStyleRangeAtOffset(position.offset);
                            if (styleRange != null) {
                                font = styleRange.font;
                            }
                        }
                        if (font == null) {
                            font = viewer.getTextWidget().getFont();
                        }
                        gc.setFont(font);
                        Point extent = gc.textExtent("\n"); //$NON-NLS-1$
                        if (extent.y > 0) {
                            int numNewlines = (int) Math.ceil(((double) height) / ((double) extent.y));
                            final int originalNewlines = numNewlines;
                            IDocument document = viewer.getDocument();
                            try {
                                for (int x = position.offset; x < document.getLength(); ++x) {
                                    if (document.getChar(x) == '\n') {
                                        if (x != position.offset
                                                && Util.annotationsIncludeOffset(viewer.getAnnotationModel(), x)) {
                                            break;
                                        }
                                        --numNewlines;
                                    } else {
                                        break;
                                    }
                                }
                                if (numNewlines > 0) {
                                    String newlines = ""; //$NON-NLS-1$
                                    for (int x = 0; x < numNewlines; ++x) {
                                        newlines += "\n"; //$NON-NLS-1$
                                    }
                                    document.replace(position.offset + 1, 0, newlines);
                                } else if (numNewlines < 0) {
                                    document.replace(position.offset, -numNewlines, ""); //$NON-NLS-1$
                                }
                                if (numNewlines != 0) {
                                    // no need to fixup other annotation positions, since the annotation model is hooked into the document.

                                    // fix up styles
                                    for (StyleRange range : ranges) {
                                        if (range.start > position.offset) {
                                            range.start += numNewlines;
                                            rangesAdjusted = true;
                                        } else if (range.start + range.length > position.offset) {
                                            range.length += numNewlines;
                                            rangesAdjusted = true;
                                        }
                                    }
                                }

                                // bug# 248643: update the annotation size to reflect the full size of the image
                                //              so that it gets repainted when some portion of the image is exposed
                                //              as a result of scrolling
                                if (position.getLength() != originalNewlines) {
                                    annotationModel.modifyAnnotationPosition(annotation,
                                            new Position(position.offset, originalNewlines));
                                }
                            } catch (BadLocationException e) {
                                // ignore
                            }
                        }
                    }
                    if (rangesAdjusted) {
                        TextPresentation presentation = new TextPresentation();
                        if (textPresentation.getDefaultStyleRange() != null) {
                            StyleRange defaultStyleRange = (StyleRange) textPresentation.getDefaultStyleRange()
                                    .clone();
                            if (viewer.getDocument() != null) {
                                if (defaultStyleRange.length < viewer.getDocument().getLength()) {
                                    defaultStyleRange.length = viewer.getDocument().getLength();
                                }
                            }
                            presentation.setDefaultStyleRange(defaultStyleRange);
                        }
                        for (StyleRange range : ranges) {
                            presentation.addStyleRange(range);
                        }
                        viewer.setTextPresentation(presentation);
                        viewer.invalidateTextPresentation();
                    }
                } finally {
                    viewer.getTextWidget().setRedraw(true);
                    gc.dispose();
                }
                viewer.getTextWidget().redraw();
            } finally {
                computingChanges = false;
            }
        }
    }

    private static final AtomicInteger resolverIdSeed = new AtomicInteger(1);

    private class ImageResolver extends Thread {

        public ImageResolver() {
            setName(ImageResolver.class.getSimpleName() + '-' + resolverIdSeed.getAndIncrement());
            setDaemon(true);
        }

        @Override
        public void run() {
            try {
                final Map<String, ImageData> urlToImageData = new HashMap<>();
                for (ImageAnnotation annotation : annotations) {
                    final String imgSrc = annotation.getUrl();
                    if (imgSrc != null && !urlToImageData.containsKey(imgSrc)) {
                        try {
                            URL location = imageCache.getBase() == null ? new URL(imgSrc)
                                    : new URL(imageCache.getBase(), imgSrc);

                            try {
                                InputStream in = new BufferedInputStream(location.openStream());
                                try {
                                    urlToImageData.put(imgSrc, new ImageData(in));
                                } catch (SWTException e) {
                                    if (e.code != SWT.ERROR_INVALID_IMAGE) {
                                        throw e;
                                    }
                                    urlToImageData.put(imgSrc, null);
                                } finally {
                                    in.close();
                                }
                            } catch (Exception e) {
                                if (WikiTextUiPlugin.getDefault() != null) {
                                    WikiTextUiPlugin.getDefault().log(IStatus.ERROR,
                                            NLS.bind(Messages.ImageManager_accessFailed, new Object[] { location }),
                                            e);
                                }
                                urlToImageData.put(imgSrc, null);
                            }
                        } catch (MalformedURLException e) {
                            urlToImageData.put(imgSrc, null);
                        }
                        display.asyncExec(new Runnable() {
                            public void run() {
                                updateImage(imgSrc, urlToImageData.get(imgSrc));
                            }
                        });
                    }
                    if (Thread.currentThread().isInterrupted()) {
                        break;
                    }
                }
            } finally {
                imageResolver = null;
            }
        }
    }

    public void documentAboutToBeChanged(DocumentEvent event) {
        if (computingChanges) {
            return;
        }
        stop();
    }

    public void documentChanged(DocumentEvent event) {
        if (computingChanges) {
            return;
        }
        inspect();
    }

    public void selectionChanged(SelectionChangedEvent event) {
        GC gc = new GC(viewer.getTextWidget());
        try {
            Event e = new Event();
            e.gc = gc;
            e.widget = viewer.getTextWidget();
            Rectangle bounds = viewer.getTextWidget().getBounds();
            e.height = bounds.height;
            e.width = bounds.width;
            e.x = 0;
            e.y = 0;
            PaintEvent paintEvent = new PaintEvent(e);
            painter.paintControl(paintEvent);
        } finally {
            gc.dispose();
        }
    }

}