/*******************************************************************************
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*******************************************************************************/
package com.liferay.ide.xml.search.ui.editor;
import com.liferay.ide.xml.search.ui.TempMarker;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IMarker;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.internal.text.html.HTMLTextPresenter;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.AbstractInformationControl;
import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenterExtension;
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.IInformationControlExtension3;
import org.eclipse.jface.text.IInformationControlExtension5;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
import org.eclipse.jface.text.source.Annotation;
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.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
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.Shell;
import org.eclipse.ui.IMarkerResolution;
import org.eclipse.ui.IMarkerResolution2;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess;
/**
* @author Kuo Zhang
* @see org.eclipse.m2e.editor.xml.internal.MarkerHoverControl.
*/
@SuppressWarnings( "restriction" )
public class LiferayCustomXmlHoverControl extends AbstractInformationControl
implements IInformationControlExtension2,
IInformationControlExtension3,
IInformationControlExtension5
{
private static final String ONE_QUICK_FIX = "1 quick fix available:";
private static final String MULTIPLE_QUICK_FIXES = "{0} quick fixes available:";
private CompoundRegion region;
private Control focusControl;
private Composite parent;
private final DefaultMarkerAnnotationAccess markerAccess;
public LiferayCustomXmlHoverControl( Shell shell )
{
super( shell, EditorsUI.getTooltipAffordanceString() );
markerAccess = new DefaultMarkerAnnotationAccess();
create();
}
private void apply( ICompletionProposal p, ITextViewer viewer, int offset, boolean isMultiFix )
{
// Focus needs to be in the text viewer, otherwise linked mode does not work
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 e = (ICompletionProposalExtension2) p;
e.apply( viewer, (char) 0, isMultiFix ? SWT.CONTROL : SWT.NONE, offset );
}
else if( p instanceof ICompletionProposalExtension )
{
ICompletionProposalExtension e = (ICompletionProposalExtension) p;
e.apply( document, (char) 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 );
}
}
catch( Exception e )
{
}
finally
{
if( target != null )
{
target.endCompoundChange();
}
}
}
private void apply( IMarkerResolution res, IMarker mark, ITextViewer viewer, int offset )
{
if( res instanceof ICompletionProposal )
{
apply( (ICompletionProposal) res, viewer, offset, false );
}
else
{
dispose();
res.run( mark );
}
}
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 );
}
private void createAnnotationInformation( Composite parent, final Annotation annotation )
{
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 );
// this paints the icon..
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 );
markerAccess.paint( annotation, e.gc, canvas, new Rectangle( 0, 0, 16, 16 ) );
}
}
);
// and now comes the text
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 Link createCompletionProposalLink( Composite parent, final IMarker mark,
final IMarkerResolution proposal, int count )
{
final boolean isMultiFix = count > 1;
if( isMultiFix )
{
new Label( parent, SWT.NONE ); // spacer to fill image cell
parent = new Composite( parent, SWT.NONE ); // indented composite for multi-fix
GridLayout layout = new GridLayout( 2, false );
layout.marginWidth = 0;
layout.marginHeight = 0;
parent.setLayout( layout );
}
Label proposalImage = new Label( parent, SWT.NONE );
proposalImage.setLayoutData( new GridData( SWT.BEGINNING, SWT.TOP, false, false ) );
Image image = null;
if( proposal instanceof ICompletionProposal )
{
image = ( (ICompletionProposal) proposal ).getImage();
}
else if( proposal instanceof IMarkerResolution2 )
{
image = ( (IMarkerResolution2) proposal ).getImage();
}
if( image != null )
{
proposalImage.setImage( image );
proposalImage.addMouseListener( new MouseAdapter()
{
public void mouseUp( MouseEvent e )
{
if( e.button == 1 )
{
apply( proposal, mark, region.textViewer, region.textOffset );
}
}
});
}
Link proposalLink = new Link( parent, SWT.WRAP );
GridData layoutData = new GridData( SWT.BEGINNING, SWT.TOP, false, false );
String linkText = proposal.getLabel();
proposalLink.setText( "<a>" + linkText + "</a>" );
proposalLink.setLayoutData( layoutData );
proposalLink.addSelectionListener
(
new SelectionAdapter()
{
public void widgetSelected( SelectionEvent e )
{
apply( proposal, mark, region.textViewer, region.textOffset );
}
}
);
return proposalLink;
}
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 );
}
private void createResolutionsControl( Composite parent, IMarker marker, IMarkerResolution[] resolutions )
{
Composite composite = new Composite( parent, SWT.NONE );
composite.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
GridLayout layout = new GridLayout( 1, false );
layout.marginWidth = 0;
layout.verticalSpacing = 2;
layout.marginHeight = 0;
composite.setLayout( layout );
Label quickFixLabel = new Label( composite, SWT.NONE );
GridData layoutData = new GridData( SWT.BEGINNING, SWT.TOP, false, false );
layoutData.horizontalIndent = 4;
quickFixLabel.setLayoutData( layoutData );
String text;
if( resolutions.length == 1 )
{
text = ONE_QUICK_FIX;
}
else
{
text = NLS.bind( MULTIPLE_QUICK_FIXES, String.valueOf( resolutions.length ) );
}
quickFixLabel.setText( text );
Composite composite2 = new Composite( parent, SWT.NONE );
composite2.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
GridLayout layout2 = new GridLayout( 2, false );
layout2.marginLeft = 5;
layout2.verticalSpacing = 2;
composite2.setLayout( layout2 );
List<Link> list = new ArrayList<Link>();
for( IMarkerResolution resolution : resolutions )
{
// Original link for single fix, hence pass 1 for count
list.add( createCompletionProposalLink( composite2, marker, resolution, 1 ) );
}
final Link[] links = list.toArray( new Link[0] );
focusControl = links[0];
for( int i = 0; i < links.length; i++ )
{
final int index = i;
final Link link = links[index];
link.addKeyListener( new KeyListener()
{
public void keyPressed( KeyEvent e )
{
switch( e.keyCode )
{
case SWT.ARROW_DOWN:
{
if( index + 1 < links.length )
{
links[index + 1].setFocus();
}
break;
}
case SWT.ARROW_UP:
{
if( index > 0 )
{
links[index - 1].setFocus();
}
break;
}
default: break;
}
}
public void keyReleased( KeyEvent e )
{
}
} );
}
}
protected void deferredCreateContent()
{
if( region != null )
{
final ScrolledComposite scrolledComposite = new ScrolledComposite( parent, SWT.V_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 );
composite.setLayout( layout );
scrolledComposite.setContent( composite );
for( IRegion reg : region.getRegions() )
{
if( reg instanceof MarkerRegion )
{
final MarkerRegion markerReg = (MarkerRegion) reg;
createAnnotationInformation( composite, markerReg.getAnnotation() );
final IMarker marker = markerReg.getAnnotation().getMarker();
IMarkerResolution[] resolutions = IDE.getMarkerHelpRegistry().getResolutions( marker );
if( resolutions.length > 0 )
{
createResolutionsControl( composite, marker, resolutions );
// createSeparator( composite );
}
}
else if( reg instanceof TemporaryRegion )
{
final TemporaryRegion tempReg = (TemporaryRegion) reg;
createAnnotationInformation( composite, tempReg.getAnnotation() );
final IMarker marker = new TempMarker( tempReg.getAnnotation() );
IMarkerResolution[] resolutions = IDE.getMarkerHelpRegistry().getResolutions( marker );
if( resolutions.length > 0 )
{
createResolutionsControl( composite, marker, resolutions );
}
}
else if( reg instanceof InfoRegion )
{
String text ;
if( ( text = ( (InfoRegion) reg ).getInfo() ) != null )
{
setInformation( composite, text );
}
}
}
Point constraints = getSizeConstraints();
Point contentSize = composite.computeSize( constraints != null ? constraints.x : SWT.DEFAULT, SWT.DEFAULT );
composite.setSize( new Point( contentSize.x, contentSize.y ) );
}
setColorAndFont( parent, parent.getForeground(), parent.getBackground(), JFaceResources.getDialogFont() );
parent.layout( true );
}
protected void disposeDeferredCreatedContent()
{
Control[] children = parent.getChildren();
for( Control child : children )
{
child.dispose();
}
ToolBarManager toolBarManager = getToolBarManager();
if( toolBarManager != null )
{
toolBarManager.removeAll();
}
}
public IInformationControlCreator getInformationPresenterControlCreator()
{
return new IInformationControlCreator()
{
public IInformationControl createInformationControl( Shell parent )
{
return new LiferayCustomXmlHoverControl( parent );
}
};
}
Shell getMyShell()
{
return super.getShell();
}
Control getRoot()
{
return parent;
}
public boolean hasContents()
{
return region != null;
}
private void setColorAndFont( Control control, Color foreground, Color background, Font font )
{
control.setForeground( foreground );
control.setBackground( background );
control.setFont( font );
if( control instanceof Composite )
{
Control[] children = ( (Composite) control ).getChildren();
for( Control child : children )
{
setColorAndFont( child, foreground, background, font );
}
}
}
public void setFocus()
{
super.setFocus();
if( focusControl != null )
{
focusControl.setFocus();
}
}
/**
* @see org.eclipse.jface.text.DefaultInformationControl#setInformation(String)
*/
private void setInformation( Composite composite, String content )
{
final HTMLTextPresenter presenter = new HTMLTextPresenter( true );
final TextPresentation presentation = new TextPresentation();
int maxWidth = -1;
int maxHeight = -1;
Point constraints = getSizeConstraints();
StyledText styledText = new StyledText( composite, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY );
int innerBorder = 1;
if( constraints != null )
{
maxWidth = constraints.x;
maxHeight = constraints.y;
if( styledText.getWordWrap() )
{
maxWidth -= innerBorder* 2;
maxHeight -= innerBorder* 2;
}
else
{
maxWidth -= innerBorder;
}
Rectangle trim = computeTrim();
maxWidth -= trim.width;
maxHeight -= trim.height;
maxWidth -= styledText.getCaret().getSize().x;
}
if( isResizable() )
{
maxHeight = Integer.MAX_VALUE;
}
if( presenter instanceof IInformationPresenterExtension )
{
content = ( (IInformationPresenterExtension) presenter ).
updatePresentation( styledText, content, presentation, maxWidth, maxHeight );
}
if( content != null )
{
styledText.setText( content );
TextPresentation.applyTextPresentation( presentation, styledText );
}
else
{
styledText.setText( "" );
}
}
public void setInput( Object input )
{
if( input instanceof CompoundRegion )
{
region = (CompoundRegion) input;
}
else
{
// throw new IllegalStateException( "Not CompoundRegion" );
return;
}
disposeDeferredCreatedContent();
deferredCreateContent();
}
public final void setVisible( boolean visible )
{
if( ! visible )
{
disposeDeferredCreatedContent();
}
super.setVisible( visible );
}
}