/******************************************************************************* * 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 ); } }