| /******************************************************************************* |
| * Copyright (c) 2000, 2010 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 |
| *******************************************************************************/ |
| package org.eclipse.jface.text.information; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.KeyListener; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| |
| import org.eclipse.core.runtime.Assert; |
| |
| import org.eclipse.jface.util.Geometry; |
| |
| import org.eclipse.jface.text.AbstractInformationControlManager; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocumentExtension3; |
| import org.eclipse.jface.text.IInformationControl; |
| import org.eclipse.jface.text.IInformationControlCreator; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.ITextViewerExtension5; |
| import org.eclipse.jface.text.IViewportListener; |
| import org.eclipse.jface.text.IWidgetTokenKeeper; |
| import org.eclipse.jface.text.IWidgetTokenKeeperExtension; |
| import org.eclipse.jface.text.IWidgetTokenOwner; |
| import org.eclipse.jface.text.IWidgetTokenOwnerExtension; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.TextUtilities; |
| |
| |
| /** |
| * Standard implementation of <code>IInformationPresenter</code>. |
| * This implementation extends <code>AbstractInformationControlManager</code>. |
| * The information control is made visible on request by calling |
| * {@link #showInformationControl(Rectangle)}. |
| * <p> |
| * Usually, clients instantiate this class and configure it before using it. The configuration |
| * must be consistent: This means the used {@link org.eclipse.jface.text.IInformationControlCreator} |
| * must create an information control expecting information in the same format the configured |
| * {@link org.eclipse.jface.text.information.IInformationProvider}s use to encode the information they provide. |
| * </p> |
| * |
| * @since 2.0 |
| */ |
| public class InformationPresenter extends AbstractInformationControlManager implements IInformationPresenter, IInformationPresenterExtension, IWidgetTokenKeeper, IWidgetTokenKeeperExtension { |
| |
| |
| /** |
| * Priority of the info controls managed by this information presenter. |
| * Default value: <code>5</code>. |
| * |
| * @since 3.0 |
| */ |
| /* |
| * 5 as value has been chosen in order to beat the hovers of {@link org.eclipse.jface.text.TextViewerHoverManager} |
| */ |
| public static final int WIDGET_PRIORITY= 5; |
| |
| |
| /** |
| * Internal information control closer. Listens to several events issued by its subject control |
| * and closes the information control when necessary. |
| */ |
| class Closer implements IInformationControlCloser, ControlListener, MouseListener, FocusListener, IViewportListener, KeyListener { |
| |
| /** The subject control. */ |
| private Control fSubjectControl; |
| /** The information control. */ |
| private IInformationControl fInformationControlToClose; |
| /** Indicates whether this closer is active. */ |
| private boolean fIsActive= false; |
| |
| @Override |
| public void setSubjectControl(Control control) { |
| fSubjectControl= control; |
| } |
| |
| @Override |
| public void setInformationControl(IInformationControl control) { |
| fInformationControlToClose= control; |
| } |
| |
| @Override |
| public void start(Rectangle informationArea) { |
| |
| if (fIsActive) |
| return; |
| fIsActive= true; |
| |
| if (fSubjectControl != null && !fSubjectControl.isDisposed()) { |
| fSubjectControl.addControlListener(this); |
| fSubjectControl.addMouseListener(this); |
| fSubjectControl.addFocusListener(this); |
| fSubjectControl.addKeyListener(this); |
| } |
| |
| if (fInformationControlToClose != null) |
| fInformationControlToClose.addFocusListener(this); |
| |
| fTextViewer.addViewportListener(this); |
| } |
| |
| @Override |
| public void stop() { |
| |
| if (!fIsActive) |
| return; |
| fIsActive= false; |
| |
| fTextViewer.removeViewportListener(this); |
| |
| if (fInformationControlToClose != null) |
| fInformationControlToClose.removeFocusListener(this); |
| |
| if (fSubjectControl != null && !fSubjectControl.isDisposed()) { |
| fSubjectControl.removeControlListener(this); |
| fSubjectControl.removeMouseListener(this); |
| fSubjectControl.removeFocusListener(this); |
| fSubjectControl.removeKeyListener(this); |
| } |
| } |
| |
| @Override |
| public void controlResized(ControlEvent e) { |
| hideInformationControl(); |
| } |
| |
| @Override |
| public void controlMoved(ControlEvent e) { |
| hideInformationControl(); |
| } |
| |
| @Override |
| public void mouseDown(MouseEvent e) { |
| hideInformationControl(); |
| } |
| |
| @Override |
| public void mouseUp(MouseEvent e) { |
| } |
| |
| @Override |
| public void mouseDoubleClick(MouseEvent e) { |
| hideInformationControl(); |
| } |
| |
| @Override |
| public void focusGained(FocusEvent e) { |
| } |
| |
| @Override |
| public void focusLost(FocusEvent e) { |
| Display d= fSubjectControl.getDisplay(); |
| d.asyncExec(new Runnable() { |
| // Without the asyncExec, mouse clicks to the workbench window are swallowed. |
| @Override |
| public void run() { |
| if (fInformationControlToClose == null || !fInformationControlToClose.isFocusControl()) |
| hideInformationControl(); |
| } |
| }); |
| } |
| |
| @Override |
| public void viewportChanged(int topIndex) { |
| hideInformationControl(); |
| } |
| |
| @Override |
| public void keyPressed(KeyEvent e) { |
| hideInformationControl(); |
| } |
| |
| @Override |
| public void keyReleased(KeyEvent e) { |
| } |
| } |
| |
| |
| /** The text viewer this information presenter works on */ |
| private ITextViewer fTextViewer; |
| /** The map of <code>IInformationProvider</code> objects */ |
| private Map<String, IInformationProvider> fProviders; |
| /** The offset to override selection. */ |
| private int fOffset= -1; |
| /** |
| * The document partitioning for this information presenter. |
| * @since 3.0 |
| */ |
| private String fPartitioning; |
| |
| /** |
| * Creates a new information presenter that uses the given information control creator. |
| * The presenter is not installed on any text viewer yet. By default, an information |
| * control closer is set that closes the information control in the event of key strokes, |
| * resizing, moves, focus changes, mouse clicks, and disposal - all of those applied to |
| * the information control's parent control. Also, the setup ensures that the information |
| * control when made visible will request the focus. By default, the default document |
| * partitioning {@link IDocumentExtension3#DEFAULT_PARTITIONING} is used. |
| * |
| * @param creator the information control creator to be used |
| */ |
| public InformationPresenter(IInformationControlCreator creator) { |
| super(creator); |
| setCloser(new Closer()); |
| takesFocusWhenVisible(true); |
| fPartitioning= IDocumentExtension3.DEFAULT_PARTITIONING; |
| } |
| |
| /** |
| * Sets the document partitioning to be used by this information presenter. |
| * |
| * @param partitioning the document partitioning to be used by this information presenter |
| * @since 3.0 |
| */ |
| public void setDocumentPartitioning(String partitioning) { |
| Assert.isNotNull(partitioning); |
| fPartitioning= partitioning; |
| } |
| |
| @Override |
| public String getDocumentPartitioning() { |
| return fPartitioning; |
| } |
| |
| /** |
| * Registers a given information provider for a particular content type. |
| * If there is already a provider registered for this type, the new provider |
| * is registered instead of the old one. |
| * |
| * @param provider the information provider to register, or <code>null</code> to remove an existing one |
| * @param contentType the content type under which to register |
| */ |
| public void setInformationProvider(IInformationProvider provider, String contentType) { |
| |
| Assert.isNotNull(contentType); |
| |
| if (fProviders == null) |
| fProviders= new HashMap<>(); |
| |
| if (provider == null) |
| fProviders.remove(contentType); |
| else |
| fProviders.put(contentType, provider); |
| } |
| |
| @Override |
| public IInformationProvider getInformationProvider(String contentType) { |
| if (fProviders == null) |
| return null; |
| |
| return fProviders.get(contentType); |
| } |
| |
| /** |
| * Sets a offset to override the selection. Setting the value to <code>-1</code> will disable |
| * overriding. |
| * |
| * @param offset the offset to override selection or <code>-1</code> |
| */ |
| public void setOffset(int offset) { |
| fOffset= offset; |
| } |
| |
| @Override |
| protected void computeInformation() { |
| |
| int offset= fOffset < 0 ? fTextViewer.getSelectedRange().x : fOffset; |
| if (offset == -1) |
| return; |
| |
| fOffset= -1; |
| |
| IInformationProvider provider= null; |
| try { |
| String contentType= TextUtilities.getContentType(fTextViewer.getDocument(), getDocumentPartitioning(), offset, true); |
| provider= getInformationProvider(contentType); |
| } catch (BadLocationException x) { |
| } |
| if (provider == null) |
| return; |
| |
| IRegion subject= provider.getSubject(fTextViewer, offset); |
| if (subject == null) |
| return; |
| |
| Object info; |
| if (provider instanceof IInformationProviderExtension) { |
| IInformationProviderExtension extension= (IInformationProviderExtension) provider; |
| info= extension.getInformation2(fTextViewer, subject); |
| } else { |
| // backward compatibility code |
| info= provider.getInformation(fTextViewer, subject); |
| } |
| |
| if (provider instanceof IInformationProviderExtension2) |
| setCustomInformationControlCreator(((IInformationProviderExtension2) provider).getInformationPresenterControlCreator()); |
| else |
| setCustomInformationControlCreator(null); |
| |
| setInformation(info, computeArea(subject)); |
| } |
| |
| /** |
| * Determines the graphical area covered by the given text region. |
| * |
| * @param region the region whose graphical extend must be computed |
| * @return the graphical extend of the given region |
| */ |
| private Rectangle computeArea(IRegion region) { |
| |
| int start= 0; |
| int end= 0; |
| |
| IRegion widgetRegion= modelRange2WidgetRange(region); |
| if (widgetRegion != null) { |
| start= widgetRegion.getOffset(); |
| end= widgetRegion.getOffset() + widgetRegion.getLength(); |
| } |
| |
| StyledText styledText= fTextViewer.getTextWidget(); |
| Rectangle bounds; |
| if (end > 0 && start < end) |
| bounds= styledText.getTextBounds(start, end - 1); |
| else { |
| Point loc= styledText.getLocationAtOffset(start); |
| bounds= new Rectangle(loc.x, loc.y, 0, styledText.getLineHeight(start)); |
| } |
| |
| Rectangle clientArea= styledText.getClientArea(); |
| Geometry.moveInside(bounds, clientArea); |
| return bounds; |
| } |
| |
| /** |
| * Translated the given range in the viewer's document into the corresponding |
| * range of the viewer's widget. |
| * |
| * @param region the range in the viewer's document |
| * @return the corresponding widget range |
| * @since 2.1 |
| */ |
| private IRegion modelRange2WidgetRange(IRegion region) { |
| if (fTextViewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer; |
| return extension.modelRange2WidgetRange(region); |
| } |
| |
| IRegion visibleRegion= fTextViewer.getVisibleRegion(); |
| int start= region.getOffset() - visibleRegion.getOffset(); |
| int end= start + region.getLength(); |
| if (end > visibleRegion.getLength()) |
| end= visibleRegion.getLength(); |
| |
| return new Region(start, end - start); |
| } |
| |
| @Override |
| public void install(ITextViewer textViewer) { |
| fTextViewer= textViewer; |
| install(fTextViewer.getTextWidget()); |
| } |
| |
| @Override |
| public void uninstall() { |
| dispose(); |
| } |
| |
| @Override |
| protected void showInformationControl(Rectangle subjectArea) { |
| if (fTextViewer instanceof IWidgetTokenOwnerExtension && fTextViewer instanceof IWidgetTokenOwner) { |
| IWidgetTokenOwnerExtension extension= (IWidgetTokenOwnerExtension) fTextViewer; |
| if (extension.requestWidgetToken(this, WIDGET_PRIORITY)) |
| super.showInformationControl(subjectArea); |
| } else if (fTextViewer instanceof IWidgetTokenOwner) { |
| IWidgetTokenOwner owner= (IWidgetTokenOwner) fTextViewer; |
| if (owner.requestWidgetToken(this)) |
| super.showInformationControl(subjectArea); |
| |
| } else |
| super.showInformationControl(subjectArea); |
| } |
| |
| @Override |
| protected void hideInformationControl() { |
| try { |
| super.hideInformationControl(); |
| } finally { |
| if (fTextViewer instanceof IWidgetTokenOwner) { |
| IWidgetTokenOwner owner= (IWidgetTokenOwner) fTextViewer; |
| owner.releaseWidgetToken(this); |
| } |
| } |
| } |
| |
| @Override |
| protected void handleInformationControlDisposed() { |
| try { |
| super.handleInformationControlDisposed(); |
| } finally { |
| if (fTextViewer instanceof IWidgetTokenOwner) { |
| IWidgetTokenOwner owner= (IWidgetTokenOwner) fTextViewer; |
| owner.releaseWidgetToken(this); |
| } |
| } |
| } |
| |
| @Override |
| public boolean requestWidgetToken(IWidgetTokenOwner owner) { |
| return false; |
| } |
| |
| @Override |
| public boolean requestWidgetToken(IWidgetTokenOwner owner, int priority) { |
| return false; |
| } |
| |
| @Override |
| public boolean setFocus(IWidgetTokenOwner owner) { |
| return false; |
| } |
| } |
| |