/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Exadel, Inc.
* Red Hat, Inc.
*******************************************************************************/
package org.jboss.tools.common.text.xml.xpl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.AbstractInformationControl;
import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IInformationControlExtension2;
import org.eclipse.jface.text.IInformationControlExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ITextHoverExtension;
import org.eclipse.jface.text.ITextHoverExtension2;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension2;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.texteditor.SimpleMarkerAnnotation;
import org.eclipse.wst.sse.ui.internal.reconcile.TemporaryAnnotation;
import org.eclipse.wst.sse.ui.internal.taginfo.ProblemAnnotationHoverProcessor;
import org.jboss.tools.common.quickfix.MarkerAnnotationInfo;
import org.jboss.tools.common.quickfix.MarkerAnnotationInfo.AnnotationInfo;
import org.jboss.tools.common.text.xml.TextXMLMessages;
public class MarkerProblemAnnotationHoverProcessor extends ProblemAnnotationHoverProcessor implements ITextHoverExtension, ITextHoverExtension2{
private IInformationControlCreator controlCreator = null;
private static IInformationControlCreator presenterControlCreator;
private HashMap<String, AnnotationInfo> annotations = new HashMap<String, AnnotationInfo>();
public String getHoverInfo(ITextViewer viewer, IRegion hoverRegion) {
return null;
}
public Object getHoverInfo2(ITextViewer viewer, IRegion hoverRegion) {
annotations.clear();
List<AnnotationInfo> all = new ArrayList<AnnotationInfo>();
List<AnnotationInfo> high = new ArrayList<AnnotationInfo>();
List<AnnotationInfo> low = new ArrayList<AnnotationInfo>();
IAnnotationModel model = ((SourceViewer) viewer).getAnnotationModel();
if(model instanceof IAnnotationModelExtension2) {
Iterator<Annotation> iterator = ((IAnnotationModelExtension2)model).getAnnotationIterator(hoverRegion.getOffset(), hoverRegion.getLength(), true, true);
while (iterator.hasNext()) {
Annotation annotation = (Annotation) iterator.next();
if (!isAnnotationValid(annotation))
continue;
Position position = model.getPosition(annotation);
AnnotationInfo info = annotations.get(annotation.getText());
if(info == null){
info = new AnnotationInfo(annotation, position);
annotations.put(annotation.getText(), info);
if(info.isTop()){
high.add(info);
}else{
low.add(info);
}
}else{
info.add(annotation);
}
}
all.addAll(high);
all.addAll(low);
}
if(all.size() > 0)
return new MarkerAnnotationInfo(all, (SourceViewer) viewer);
return null;
}
public IInformationControlCreator getInformationPresenterControlCreator() {
if (presenterControlCreator == null)
presenterControlCreator= new PresenterControlCreator();
return presenterControlCreator;
}
protected boolean isAnnotationValid(Annotation annotation) {
if(annotation.getText() != null && (annotation instanceof SimpleMarkerAnnotation || annotation instanceof TemporaryAnnotation))
return true;
return false;
}
public IInformationControlCreator getHoverControlCreator() {
if (controlCreator == null)
controlCreator= new AnnotationHoverControlCreator(getInformationPresenterControlCreator());
return controlCreator;
}
private static class MarkerAnnotationInformationControl extends AbstractInformationControl implements IInformationControlExtension2{
private final DefaultMarkerAnnotationAccess annotationAccess;
private MarkerAnnotationInfo info;
private Composite parent;
public MarkerAnnotationInformationControl(Shell parentShell,
String str) {
super(parentShell, str);
annotationAccess= new DefaultMarkerAnnotationAccess();
create();
}
public boolean hasContents() {
return info != null;
}
private MarkerAnnotationInfo getAnnotationInfo() {
return info;
}
@Override
protected void createContent(Composite parent) {
this.parent = parent;
GridLayout layout= new GridLayout(1, false);
layout.verticalSpacing= 0;
layout.marginWidth= 0;
layout.marginHeight= 0;
parent.setLayout(layout);
}
public final void setVisible(boolean visible) {
if (!visible)
disposeContent();
super.setVisible(visible);
}
public Point computeSizeHint() {
Point preferedSize= getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
Point constrains= getSizeConstraints();
if (constrains == null)
return preferedSize;
Point constrainedSize= getShell().computeSize(constrains.x, SWT.DEFAULT, true);
int width= Math.min(preferedSize.x, constrainedSize.x);
int height= Math.max(preferedSize.y, constrainedSize.y);
return new Point(width, height);
}
public void setInput(Object input) {
Assert.isLegal(input instanceof MarkerAnnotationInfo);
info = (MarkerAnnotationInfo)input;
disposeContent();
createContent();
}
protected void disposeContent() {
for (Control child : parent.getChildren()) {
child.dispose();
}
}
protected void createContent() {
boolean first = true;
final ScrolledComposite scrolledComposite= new ScrolledComposite(parent, SWT.V_SCROLL | SWT.H_SCROLL);
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
scrolledComposite.setLayoutData(gridData);
scrolledComposite.setExpandVertical(false);
scrolledComposite.setExpandHorizontal(false);
Composite composite = new Composite(scrolledComposite, SWT.NONE);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
GridLayout layout = new GridLayout(1, false);
layout.marginLeft = 5;
layout.verticalSpacing = 2;
composite.setLayout(layout);
for(AnnotationInfo info : getAnnotationInfo().infos){
createInfo(composite, info.getMainAnnotation(), first);
if(first)
first = false;
List<ICompletionProposal> proposals = getAnnotationInfo().getCompletionProposals(info);
if (proposals.size() > 0){
createControl(composite, proposals);
}
}
scrolledComposite.setContent(composite);
setDecoration(scrolledComposite, parent.getForeground(), parent.getBackground(), JFaceResources.getDialogFont());
Point contentSize= composite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
composite.setSize(contentSize);
Point constraints= getSizeConstraints();
if (constraints != null && contentSize.x < constraints.x) {
ScrollBar horizontalBar= scrolledComposite.getHorizontalBar();
int scrollBarHeight;
if (horizontalBar == null) {
Point scrollSize = scrolledComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
scrollBarHeight = scrollSize.y - contentSize.y;
} else {
scrollBarHeight = horizontalBar.getSize().y;
}
gridData.heightHint = contentSize.y - scrollBarHeight;
}
parent.layout(true);
}
private void createControl(Composite parent, List<ICompletionProposal> proposals) {
Composite composite = new Composite(parent, SWT.NONE);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
GridLayout layout2 = new GridLayout(1, false);
layout2.verticalSpacing = 2;
layout2.marginLeft = 5;
composite.setLayout(layout2);
Label separator= new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
GridData gridData= new GridData(SWT.FILL, SWT.CENTER, true, false);
separator.setLayoutData(gridData);
Label quickFixLabel= new Label(composite, SWT.NONE);
GridData layoutData= new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
layoutData.horizontalIndent = 4;
quickFixLabel.setLayoutData(layoutData);
String text;
if (proposals.size() == 1) {
text= TextXMLMessages.SINGLE_QUICK_FIX;
} else {
text= NLS.bind(TextXMLMessages.MULTIPLE_QUICK_FIX, proposals.size());
}
quickFixLabel.setText(text);
createList(composite, proposals);
}
private void createList(Composite parent, List<ICompletionProposal> proposals) {
List<Link> list = new ArrayList<Link>();
for (ICompletionProposal proposal : proposals) {
list.add(createLink(parent, proposal));
}
}
private Link createLink(Composite parent, final ICompletionProposal proposal) {
parent= new Composite(parent, SWT.NONE);
GridLayout layout= new GridLayout(2, false);
layout.marginWidth= 0;
layout.marginHeight= 1;
parent.setLayout(layout);
Label proposalImage= new Label(parent, SWT.NONE);
proposalImage.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
Image image= proposal.getImage();
if (image != null) {
proposalImage.setImage(image);
proposalImage.addMouseListener(new MouseListener() {
public void mouseDoubleClick(MouseEvent e) {
}
public void mouseDown(MouseEvent e) {
fix(proposal, info.viewer, info.infos.get(0).getPosition().getOffset());
}
public void mouseUp(MouseEvent e) {
}
});
}
Link proposalLink = new Link(parent, SWT.WRAP);
GridData layoutData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
String linkText = proposal.getDisplayString();
proposalLink.setText("<a>" + linkText + "</a>"); //$NON-NLS-1$ //$NON-NLS-2$
proposalLink.setLayoutData(layoutData);
proposalLink.addMouseListener(new MouseListener(){
public void mouseDoubleClick(MouseEvent e) {
}
public void mouseDown(MouseEvent e) {
fix(proposal, info.viewer, info.infos.get(0).getPosition().getOffset());
}
public void mouseUp(MouseEvent e) {
}
});
return proposalLink;
}
private void fix(ICompletionProposal p, ITextViewer viewer, int offset) {
dispose();
IRewriteTarget target = null;
try {
IDocument document = viewer.getDocument();
if (viewer instanceof ITextViewerExtension) {
ITextViewerExtension extension= (ITextViewerExtension) viewer;
target= extension.getRewriteTarget();
}
if (target != null)
target.beginCompoundChange();
if(p instanceof ICompletionProposalExtension2){
((ICompletionProposalExtension2)p).apply(viewer, (char) 0, 0, offset);
}else{
p.apply(document);
}
Point selection = p.getSelection(document);
if (selection != null) {
viewer.setSelectedRange(selection.x, selection.y);
viewer.revealRange(selection.x, selection.y);
}
} finally {
if (target != null)
target.endCompoundChange();
}
}
private void createInfo(Composite parent, final Annotation annotation, boolean firstElement) {
if(!firstElement){
Label separator= new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
GridData gridData= new GridData(SWT.FILL, SWT.CENTER, true, false);
separator.setLayoutData(gridData);
}
Composite composite = new Composite(parent, SWT.NONE);
composite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
GridLayout layout = new GridLayout(2, false);
layout.marginHeight = 2;
layout.marginWidth = 2;
layout.horizontalSpacing = 0;
composite.setLayout(layout);
final Canvas canvas = new Canvas(composite, SWT.NO_FOCUS);
GridData gridData = new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false);
gridData.widthHint = 17;
gridData.heightHint = 16;
canvas.setLayoutData(gridData);
canvas.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
e.gc.setFont(null);
annotationAccess.paint(annotation, e.gc, canvas, new Rectangle(0, 0, 16, 16));
}
});
StyledText text = new StyledText(composite, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY);
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
text.setLayoutData(data);
String annotationText = annotation.getText();
if (annotationText != null)
text.setText(annotationText);
}
private void setDecoration(Control control, Color foreground, Color background, Font font) {
control.setForeground(foreground);
control.setBackground(background);
control.setFont(font);
if (control instanceof Composite) {
for (Control child : ((Composite) control).getChildren()) {
setDecoration(child, foreground, background, font);
}
}
}
}
private static final class AnnotationHoverControlCreator extends AbstractReusableInformationControlCreator {
private final IInformationControlCreator presenterControlCreator;
public AnnotationHoverControlCreator(IInformationControlCreator presenterControlCreator) {
this.presenterControlCreator = presenterControlCreator;
}
public IInformationControl doCreateInformationControl(Shell parent) {
return new MarkerAnnotationInformationControl(parent, EditorsUI.getTooltipAffordanceString()) {
public IInformationControlCreator getInformationPresenterControlCreator() {
return presenterControlCreator;
}
};
}
public boolean canReuse(IInformationControl control) {
if (!super.canReuse(control))
return false;
if (control instanceof IInformationControlExtension4)
((IInformationControlExtension4) control).setStatusText(EditorsUI.getTooltipAffordanceString());
return true;
}
}
private static final class PresenterControlCreator extends AbstractReusableInformationControlCreator {
public IInformationControl doCreateInformationControl(Shell parent) {
return new MarkerAnnotationInformationControl(parent, EditorsUI.getTooltipAffordanceString()) {
public IInformationControlCreator getInformationPresenterControlCreator() {
return presenterControlCreator;
}
};
}
}
}