001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.text.information;
011:
012: import java.util.HashMap;
013: import java.util.Map;
014:
015: import org.eclipse.swt.custom.StyledText;
016: import org.eclipse.swt.events.ControlEvent;
017: import org.eclipse.swt.events.ControlListener;
018: import org.eclipse.swt.events.FocusEvent;
019: import org.eclipse.swt.events.FocusListener;
020: import org.eclipse.swt.events.KeyEvent;
021: import org.eclipse.swt.events.KeyListener;
022: import org.eclipse.swt.events.MouseEvent;
023: import org.eclipse.swt.events.MouseListener;
024: import org.eclipse.swt.graphics.Point;
025: import org.eclipse.swt.graphics.Rectangle;
026: import org.eclipse.swt.widgets.Control;
027: import org.eclipse.swt.widgets.Display;
028:
029: import org.eclipse.core.runtime.Assert;
030:
031: import org.eclipse.jface.text.AbstractInformationControlManager;
032: import org.eclipse.jface.text.BadLocationException;
033: import org.eclipse.jface.text.IDocumentExtension3;
034: import org.eclipse.jface.text.IInformationControl;
035: import org.eclipse.jface.text.IInformationControlCreator;
036: import org.eclipse.jface.text.IRegion;
037: import org.eclipse.jface.text.ITextViewer;
038: import org.eclipse.jface.text.ITextViewerExtension5;
039: import org.eclipse.jface.text.IViewportListener;
040: import org.eclipse.jface.text.IWidgetTokenKeeper;
041: import org.eclipse.jface.text.IWidgetTokenKeeperExtension;
042: import org.eclipse.jface.text.IWidgetTokenOwner;
043: import org.eclipse.jface.text.IWidgetTokenOwnerExtension;
044: import org.eclipse.jface.text.Region;
045: import org.eclipse.jface.text.TextUtilities;
046:
047: /**
048: * Standard implementation of <code>IInformationPresenter</code>.
049: * This implementation extends <code>AbstractInformationControlManager</code>.
050: * The information control is made visible on request by calling
051: * {@link #showInformationControl(Rectangle)}.
052: * <p>
053: * Usually, clients instantiate this class and configure it before using it. The configuration
054: * must be consistent: This means the used {@link org.eclipse.jface.text.IInformationControlCreator}
055: * must create an information control expecting information in the same format the configured
056: * {@link org.eclipse.jface.text.information.IInformationProvider}s use to encode the information they provide.
057: * </p>
058: *
059: * @since 2.0
060: */
061: public class InformationPresenter extends
062: AbstractInformationControlManager implements
063: IInformationPresenter, IInformationPresenterExtension,
064: IWidgetTokenKeeper, IWidgetTokenKeeperExtension {
065:
066: /**
067: * Priority of the info controls managed by this information presenter.
068: * Default value: <code>5</code>.
069: *
070: * @since 3.0
071: */
072: /*
073: * 5 as value has been chosen in order to beat the hovers of {@link org.eclipse.jface.text.TextViewerHoverManager}
074: */
075: public static final int WIDGET_PRIORITY = 5;
076:
077: /**
078: * Internal information control closer. Listens to several events issued by its subject control
079: * and closes the information control when necessary.
080: */
081: class Closer implements IInformationControlCloser, ControlListener,
082: MouseListener, FocusListener, IViewportListener,
083: KeyListener {
084:
085: /** The subject control. */
086: private Control fSubjectControl;
087: /** The information control. */
088: private IInformationControl fInformationControlToClose;
089: /** Indicates whether this closer is active. */
090: private boolean fIsActive = false;
091:
092: /*
093: * @see IInformationControlCloser#setSubjectControl(Control)
094: */
095: public void setSubjectControl(Control control) {
096: fSubjectControl = control;
097: }
098:
099: /*
100: * @see IInformationControlCloser#setInformationControl(IInformationControl)
101: */
102: public void setInformationControl(IInformationControl control) {
103: fInformationControlToClose = control;
104: }
105:
106: /*
107: * @see IInformationControlCloser#start(Rectangle)
108: */
109: public void start(Rectangle informationArea) {
110:
111: if (fIsActive)
112: return;
113: fIsActive = true;
114:
115: if (fSubjectControl != null
116: && !fSubjectControl.isDisposed()) {
117: fSubjectControl.addControlListener(this );
118: fSubjectControl.addMouseListener(this );
119: fSubjectControl.addFocusListener(this );
120: fSubjectControl.addKeyListener(this );
121: }
122:
123: if (fInformationControlToClose != null)
124: fInformationControlToClose.addFocusListener(this );
125:
126: fTextViewer.addViewportListener(this );
127: }
128:
129: /*
130: * @see IInformationControlCloser#stop()
131: */
132: public void stop() {
133:
134: if (!fIsActive)
135: return;
136: fIsActive = false;
137:
138: fTextViewer.removeViewportListener(this );
139:
140: if (fInformationControlToClose != null)
141: fInformationControlToClose.removeFocusListener(this );
142:
143: hideInformationControl();
144:
145: if (fSubjectControl != null
146: && !fSubjectControl.isDisposed()) {
147: fSubjectControl.removeControlListener(this );
148: fSubjectControl.removeMouseListener(this );
149: fSubjectControl.removeFocusListener(this );
150: fSubjectControl.removeKeyListener(this );
151: }
152: }
153:
154: /*
155: * @see ControlListener#controlResized(ControlEvent)
156: */
157: public void controlResized(ControlEvent e) {
158: stop();
159: }
160:
161: /*
162: * @see ControlListener#controlMoved(ControlEvent)
163: */
164: public void controlMoved(ControlEvent e) {
165: stop();
166: }
167:
168: /*
169: * @see MouseListener#mouseDown(MouseEvent)
170: */
171: public void mouseDown(MouseEvent e) {
172: stop();
173: }
174:
175: /*
176: * @see MouseListener#mouseUp(MouseEvent)
177: */
178: public void mouseUp(MouseEvent e) {
179: }
180:
181: /*
182: * @see MouseListener#mouseDoubleClick(MouseEvent)
183: */
184: public void mouseDoubleClick(MouseEvent e) {
185: stop();
186: }
187:
188: /*
189: * @see FocusListener#focusGained(FocusEvent)
190: */
191: public void focusGained(FocusEvent e) {
192: }
193:
194: /*
195: * @see FocusListener#focusLost(FocusEvent)
196: */
197: public void focusLost(FocusEvent e) {
198: Display d = fSubjectControl.getDisplay();
199: d.asyncExec(new Runnable() {
200: public void run() {
201: if (fInformationControlToClose == null
202: || !fInformationControlToClose
203: .isFocusControl())
204: stop();
205: }
206: });
207: }
208:
209: /*
210: * @see IViewportListenerListener#viewportChanged(int)
211: */
212: public void viewportChanged(int topIndex) {
213: stop();
214: }
215:
216: /*
217: * @see KeyListener#keyPressed(KeyEvent)
218: */
219: public void keyPressed(KeyEvent e) {
220: stop();
221: }
222:
223: /*
224: * @see KeyListener#keyReleased(KeyEvent)
225: */
226: public void keyReleased(KeyEvent e) {
227: }
228: }
229:
230: /** The text viewer this information presenter works on */
231: private ITextViewer fTextViewer;
232: /** The map of <code>IInformationProvider</code> objects */
233: private Map fProviders;
234: /** The offset to override selection. */
235: private int fOffset = -1;
236: /**
237: * The document partitioning for this information presenter.
238: * @since 3.0
239: */
240: private String fPartitioning;
241:
242: /**
243: * Creates a new information presenter that uses the given information control creator.
244: * The presenter is not installed on any text viewer yet. By default, an information
245: * control closer is set that closes the information control in the event of key strokes,
246: * resizing, moves, focus changes, mouse clicks, and disposal - all of those applied to
247: * the information control's parent control. Also, the setup ensures that the information
248: * control when made visible will request the focus. By default, the default document
249: * partitioning {@link IDocumentExtension3#DEFAULT_PARTITIONING} is used.
250: *
251: * @param creator the information control creator to be used
252: */
253: public InformationPresenter(IInformationControlCreator creator) {
254: super (creator);
255: setCloser(new Closer());
256: takesFocusWhenVisible(true);
257: fPartitioning = IDocumentExtension3.DEFAULT_PARTITIONING;
258: }
259:
260: /**
261: * Sets the document partitioning to be used by this information presenter.
262: *
263: * @param partitioning the document partitioning to be used by this information presenter
264: * @since 3.0
265: */
266: public void setDocumentPartitioning(String partitioning) {
267: Assert.isNotNull(partitioning);
268: fPartitioning = partitioning;
269: }
270:
271: /*
272: * @see org.eclipse.jface.text.information.IInformationPresenterExtension#getDocumentPartitioning()
273: * @since 3.0
274: */
275: public String getDocumentPartitioning() {
276: return fPartitioning;
277: }
278:
279: /**
280: * Registers a given information provider for a particular content type.
281: * If there is already a provider registered for this type, the new provider
282: * is registered instead of the old one.
283: *
284: * @param provider the information provider to register, or <code>null</code> to remove an existing one
285: * @param contentType the content type under which to register
286: */
287: public void setInformationProvider(IInformationProvider provider,
288: String contentType) {
289:
290: Assert.isNotNull(contentType);
291:
292: if (fProviders == null)
293: fProviders = new HashMap();
294:
295: if (provider == null)
296: fProviders.remove(contentType);
297: else
298: fProviders.put(contentType, provider);
299: }
300:
301: /*
302: * @see IInformationPresenter#getInformationProvider(String)
303: */
304: public IInformationProvider getInformationProvider(
305: String contentType) {
306: if (fProviders == null)
307: return null;
308:
309: return (IInformationProvider) fProviders.get(contentType);
310: }
311:
312: /**
313: * Sets a offset to override the selection. Setting the value to <code>-1</code> will disable
314: * overriding.
315: *
316: * @param offset the offset to override selection or <code>-1</code>
317: */
318: public void setOffset(int offset) {
319: fOffset = offset;
320: }
321:
322: /*
323: * @see AbstractInformationControlManager#computeInformation()
324: */
325: protected void computeInformation() {
326:
327: int offset = fOffset < 0 ? fTextViewer.getSelectedRange().x
328: : fOffset;
329: if (offset == -1)
330: return;
331:
332: fOffset = -1;
333:
334: IInformationProvider provider = null;
335: try {
336: String contentType = TextUtilities.getContentType(
337: fTextViewer.getDocument(),
338: getDocumentPartitioning(), offset, true);
339: provider = getInformationProvider(contentType);
340: } catch (BadLocationException x) {
341: }
342: if (provider == null)
343: return;
344:
345: IRegion subject = provider.getSubject(fTextViewer, offset);
346: if (subject == null)
347: return;
348:
349: if (provider instanceof IInformationProviderExtension2)
350: setCustomInformationControlCreator(((IInformationProviderExtension2) provider)
351: .getInformationPresenterControlCreator());
352: else
353: setCustomInformationControlCreator(null);
354:
355: if (provider instanceof IInformationProviderExtension) {
356: IInformationProviderExtension extension = (IInformationProviderExtension) provider;
357: setInformation(extension.getInformation2(fTextViewer,
358: subject), computeArea(subject));
359: } else {
360: // backward compatibility code
361: setInformation(provider
362: .getInformation(fTextViewer, subject),
363: computeArea(subject));
364: }
365: }
366:
367: /**
368: * Determines the graphical area covered by the given text region.
369: *
370: * @param region the region whose graphical extend must be computed
371: * @return the graphical extend of the given region
372: */
373: private Rectangle computeArea(IRegion region) {
374:
375: int start = 0;
376: int end = 0;
377:
378: IRegion widgetRegion = modelRange2WidgetRange(region);
379: if (widgetRegion != null) {
380: start = widgetRegion.getOffset();
381: end = widgetRegion.getOffset() + widgetRegion.getLength();
382: }
383:
384: StyledText styledText = fTextViewer.getTextWidget();
385: Rectangle bounds;
386: if (end > 0 && start < end)
387: bounds = styledText.getTextBounds(start, end - 1);
388: else {
389: Point loc = styledText.getLocationAtOffset(start);
390: bounds = new Rectangle(loc.x, loc.y, 0, styledText
391: .getLineHeight(start));
392: }
393:
394: return bounds;
395: }
396:
397: /**
398: * Translated the given range in the viewer's document into the corresponding
399: * range of the viewer's widget.
400: *
401: * @param region the range in the viewer's document
402: * @return the corresponding widget range
403: * @since 2.1
404: */
405: private IRegion modelRange2WidgetRange(IRegion region) {
406: if (fTextViewer instanceof ITextViewerExtension5) {
407: ITextViewerExtension5 extension = (ITextViewerExtension5) fTextViewer;
408: return extension.modelRange2WidgetRange(region);
409: }
410:
411: IRegion visibleRegion = fTextViewer.getVisibleRegion();
412: int start = region.getOffset() - visibleRegion.getOffset();
413: int end = start + region.getLength();
414: if (end > visibleRegion.getLength())
415: end = visibleRegion.getLength();
416:
417: return new Region(start, end - start);
418: }
419:
420: /*
421: * @see IInformationPresenter#install(ITextViewer)
422: */
423: public void install(ITextViewer textViewer) {
424: fTextViewer = textViewer;
425: install(fTextViewer.getTextWidget());
426: }
427:
428: /*
429: * @see IInformationPresenter#uninstall()
430: */
431: public void uninstall() {
432: dispose();
433: }
434:
435: /*
436: * @see AbstractInformationControlManager#showInformationControl(Rectangle)
437: */
438: protected void showInformationControl(Rectangle subjectArea) {
439: if (fTextViewer instanceof IWidgetTokenOwnerExtension) {
440: IWidgetTokenOwnerExtension extension = (IWidgetTokenOwnerExtension) fTextViewer;
441: if (extension.requestWidgetToken(this , WIDGET_PRIORITY))
442: super .showInformationControl(subjectArea);
443: } else if (fTextViewer instanceof IWidgetTokenOwner) {
444: IWidgetTokenOwner owner = (IWidgetTokenOwner) fTextViewer;
445: if (owner.requestWidgetToken(this ))
446: super .showInformationControl(subjectArea);
447:
448: }
449: }
450:
451: /*
452: * @see AbstractInformationControlManager#hideInformationControl()
453: */
454: protected void hideInformationControl() {
455: try {
456: super .hideInformationControl();
457: } finally {
458: if (fTextViewer instanceof IWidgetTokenOwner) {
459: IWidgetTokenOwner owner = (IWidgetTokenOwner) fTextViewer;
460: owner.releaseWidgetToken(this );
461: }
462: }
463: }
464:
465: /*
466: * @see AbstractInformationControlManager#handleInformationControlDisposed()
467: */
468: protected void handleInformationControlDisposed() {
469: try {
470: super .handleInformationControlDisposed();
471: } finally {
472: if (fTextViewer instanceof IWidgetTokenOwner) {
473: IWidgetTokenOwner owner = (IWidgetTokenOwner) fTextViewer;
474: owner.releaseWidgetToken(this );
475: }
476: }
477: }
478:
479: /*
480: * @see org.eclipse.jface.text.IWidgetTokenKeeper#requestWidgetToken(IWidgetTokenOwner)
481: */
482: public boolean requestWidgetToken(IWidgetTokenOwner owner) {
483: return false;
484: }
485:
486: /*
487: * @see org.eclipse.jface.text.IWidgetTokenKeeperExtension#requestWidgetToken(org.eclipse.jface.text.IWidgetTokenOwner, int)
488: * @since 3.0
489: */
490: public boolean requestWidgetToken(IWidgetTokenOwner owner,
491: int priority) {
492: return false;
493: }
494:
495: /*
496: * @see org.eclipse.jface.text.IWidgetTokenKeeperExtension#setFocus(org.eclipse.jface.text.IWidgetTokenOwner)
497: * @since 3.0
498: */
499: public boolean setFocus(IWidgetTokenOwner owner) {
500: return false;
501: }
502: }
|