1 /*******************************************************************************
2 * Copyright (c) 2000, 2006 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text.java.hover;
13 import java.util.ArrayList;
14 import java.util.Iterator;
15 import java.util.List;
17 import org.eclipse.jface.text.AbstractInformationControlManager;
18 import org.eclipse.jface.text.DefaultInformationControl;
19 import org.eclipse.jface.text.IInformationControl;
20 import org.eclipse.jface.text.IInformationControlCreator;
21 import org.eclipse.jface.text.IInformationControlExtension;
22 import org.eclipse.jface.text.IInformationControlExtension2;
23 import org.eclipse.jface.text.IRegion;
24 import org.eclipse.jface.text.IViewportListener;
25 import org.eclipse.jface.text.Position;
26 import org.eclipse.jface.text.Region;
27 import org.eclipse.jface.text.TextViewer;
28 import org.eclipse.jface.text.source.Annotation;
29 import org.eclipse.jface.text.source.IAnnotationAccess;
30 import org.eclipse.jface.text.source.IAnnotationAccessExtension;
31 import org.eclipse.jface.text.source.IAnnotationModel;
32 import org.eclipse.jface.text.source.ISourceViewer;
33 import org.eclipse.jface.text.source.IVerticalRulerInfo;
34 import org.eclipse.jface.text.source.IVerticalRulerListener;
35 import org.eclipse.jface.text.source.VerticalRulerEvent;
36 import org.eclipse.jface.viewers.IDoubleClickListener;
37 import org.eclipse.swt.SWT;
38 import org.eclipse.swt.custom.StyleRange;
39 import org.eclipse.swt.custom.StyledText;
40 import org.eclipse.swt.events.DisposeEvent;
41 import org.eclipse.swt.events.DisposeListener;
42 import org.eclipse.swt.events.FocusListener;
43 import org.eclipse.swt.events.MenuEvent;
44 import org.eclipse.swt.events.MenuListener;
45 import org.eclipse.swt.events.MouseAdapter;
46 import org.eclipse.swt.events.MouseEvent;
47 import org.eclipse.swt.events.MouseTrackAdapter;
48 import org.eclipse.swt.events.MouseTrackListener;
49 import org.eclipse.swt.events.PaintEvent;
50 import org.eclipse.swt.events.PaintListener;
51 import org.eclipse.swt.graphics.Color;
52 import org.eclipse.swt.graphics.Cursor;
53 import org.eclipse.swt.graphics.Point;
54 import org.eclipse.swt.graphics.Rectangle;
55 import org.eclipse.swt.layout.GridData;
56 import org.eclipse.swt.layout.GridLayout;
57 import org.eclipse.swt.widgets.Canvas;
58 import org.eclipse.swt.widgets.Composite;
59 import org.eclipse.swt.widgets.Control;
60 import org.eclipse.swt.widgets.Display;
61 import org.eclipse.swt.widgets.Event;
62 import org.eclipse.swt.widgets.Layout;
63 import org.eclipse.swt.widgets.Listener;
64 import org.eclipse.swt.widgets.Menu;
65 import org.eclipse.swt.widgets.Shell;
66 import org.eclipse.swt.widgets.Widget;
69 * A control that can display a number of annotations. The control can decide
70 * how it layouts the annotations to present them to the user.
72 * This class got moved here form Platform Text since it was not used there and
73 * caused discouraged access warnings. It will be moved down again once
74 * annotation roll-over support is provided by Platform Text.
77 * Each annotation can have its custom context menu and hover.
82 public class AnnotationExpansionControl implements IInformationControl,
83 IInformationControlExtension, IInformationControlExtension2 {
85 public interface ICallback {
86 void run(IInformationControlExtension2 control);
90 * Input used by the control to display the annotations. TODO move to
91 * top-level class TODO encapsulate fields
95 public static class AnnotationHoverInput {
96 public Annotation[] fAnnotations;
98 public ISourceViewer fViewer;
100 public IVerticalRulerInfo fRulerInfo;
102 public IVerticalRulerListener fAnnotationListener;
104 public IDoubleClickListener fDoubleClickListener;
106 public ICallback redoAction;
108 public IAnnotationModel model;
111 private final class Item {
112 Annotation fAnnotation;
116 StyleRange[] oldStyles;
118 public void selected() {
119 Display disp = fShell.getDisplay();
120 canvas.setCursor(fHandCursor);
121 // TODO: shade - for now: set grey background
122 canvas.setBackground(getSelectionColor(disp));
124 // highlight the viewer background at its position
125 oldStyles = setViewerBackground(fAnnotation);
130 if (fHoverManager != null)
131 fHoverManager.showInformation();
133 if (fInput.fAnnotationListener != null) {
134 VerticalRulerEvent event = new VerticalRulerEvent(fAnnotation);
135 fInput.fAnnotationListener.annotationSelected(event);
140 public void defaultSelected() {
141 if (fInput.fAnnotationListener != null) {
142 VerticalRulerEvent event = new VerticalRulerEvent(fAnnotation);
143 fInput.fAnnotationListener.annotationDefaultSelected(event);
149 public void showContextMenu(Menu menu) {
150 if (fInput.fAnnotationListener != null) {
151 VerticalRulerEvent event = new VerticalRulerEvent(fAnnotation);
152 fInput.fAnnotationListener.annotationContextMenuAboutToShow(
157 public void deselect() {
159 // fHoverManager.disposeInformationControl();
164 resetViewerBackground(oldStyles);
167 Display disp = fShell.getDisplay();
168 canvas.setCursor(null);
169 // TODO: remove shading - for now: set standard background
172 .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
179 * Disposes of an item
181 private final static class MyDisposeListener implements DisposeListener {
183 * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
185 public void widgetDisposed(DisposeEvent e) {
186 Item item = (Item) ((Widget) e.getSource()).getData();
189 item.fAnnotation = null;
190 item.oldStyles = null;
192 ((Widget) e.getSource()).setData(null);
197 * Listener on context menu invocation on the items
199 private final class MyMenuDetectListener implements Listener {
201 * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
203 public void handleEvent(Event event) {
204 if (event.type == SWT.MenuDetect) {
205 // TODO: show per-item menu
206 // for now: show ruler context menu
207 if (fInput != null) {
208 Control ruler = fInput.fRulerInfo.getControl();
209 if (ruler != null && !ruler.isDisposed()) {
210 Menu menu = ruler.getMenu();
211 if (menu != null && !menu.isDisposed()) {
212 menu.setLocation(event.x, event.y);
213 menu.addMenuListener(new MenuListener() {
215 public void menuHidden(MenuEvent e) {
219 public void menuShown(MenuEvent e) {
223 menu.setVisible(true);
232 * Listener on mouse events on the items.
234 private final class MyMouseListener extends MouseAdapter {
236 * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
238 public void mouseDoubleClick(MouseEvent e) {
239 Item item = (Item) ((Widget) e.getSource()).getData();
240 if (e.button == 1 && item.fAnnotation == fInput.fAnnotations[0]
241 && fInput.fDoubleClickListener != null) {
242 fInput.fDoubleClickListener.doubleClick(null);
243 // special code for JDT to renew the annotation set.
244 if (fInput.redoAction != null)
245 fInput.redoAction.run(AnnotationExpansionControl.this);
248 // TODO special action to invoke double-click action on the vertical
251 // Canvas can= (Canvas) e.getSource();
252 // Annotation a= (Annotation) can.getData();
254 // a.getDoubleClickAction().run();
259 * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
261 public void mouseUp(MouseEvent e) {
262 Item item = (Item) ((Widget) e.getSource()).getData();
263 // TODO for now, to make double click work: disable single click on
265 // disable later when the annotationlistener selectively handles
267 if (item != null && e.button == 1) // && item.fAnnotation !=
268 // fInput.fAnnotations[0])
269 item.defaultSelected();
273 * @see org.eclipse.swt.events.MouseAdapter#mouseDown(org.eclipse.swt.events.MouseEvent)
275 public void mouseDown(MouseEvent e) {
281 * Listener on mouse track events on the items.
283 private final class MyMouseTrackListener implements MouseTrackListener {
285 * @see org.eclipse.swt.events.MouseTrackListener#mouseEnter(org.eclipse.swt.events.MouseEvent)
287 public void mouseEnter(MouseEvent e) {
288 Item item = (Item) ((Widget) e.getSource()).getData();
294 * @see org.eclipse.swt.events.MouseTrackListener#mouseExit(org.eclipse.swt.events.MouseEvent)
296 public void mouseExit(MouseEvent e) {
298 Item item = (Item) ((Widget) e.getSource()).getData();
302 // if the event lies outside the entire popup, dispose
303 org.eclipse.swt.graphics.Region region = fShell.getRegion();
304 Canvas can = (Canvas) e.getSource();
305 Point p = can.toDisplay(e.x, e.y);
306 if (region == null) {
307 Rectangle bounds = fShell.getBounds();
308 // p= fShell.toControl(p);
309 if (!bounds.contains(p))
312 p = fShell.toControl(p);
313 if (!region.contains(p))
320 * @see org.eclipse.swt.events.MouseTrackListener#mouseHover(org.eclipse.swt.events.MouseEvent)
322 public void mouseHover(MouseEvent e) {
323 if (fHoverManager == null) {
324 fHoverManager = new HoverManager();
325 fHoverManager.takesFocusWhenVisible(false);
326 fHoverManager.install(fComposite);
327 fHoverManager.showInformation();
337 public class LinearLayouter {
339 private static final int ANNOTATION_SIZE = 14;
341 private static final int BORDER_WIDTH = 2;
343 public Layout getLayout(int itemCount) {
344 // simple layout: a row of items
345 GridLayout layout = new GridLayout(itemCount, true);
346 layout.horizontalSpacing = 1;
347 layout.verticalSpacing = 0;
348 layout.marginHeight = 1;
349 layout.marginWidth = 1;
353 public Object getLayoutData() {
354 GridData gridData = new GridData(
355 ANNOTATION_SIZE + 2 * BORDER_WIDTH, ANNOTATION_SIZE + 2
357 gridData.horizontalAlignment = GridData.CENTER;
358 gridData.verticalAlignment = GridData.CENTER;
362 public int getAnnotationSize() {
363 return ANNOTATION_SIZE;
366 public int getBorderWidth() {
370 public org.eclipse.swt.graphics.Region getShellRegion(int itemCount) {
371 // no special region - set to null for default shell size
378 * Listener on paint events on the items. Paints the annotation image on the
379 * given <code>GC</code>.
381 private final class MyPaintListener implements PaintListener {
383 * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
385 public void paintControl(PaintEvent e) {
386 Canvas can = (Canvas) e.getSource();
387 Annotation a = ((Item) can.getData()).fAnnotation;
389 Rectangle rect = new Rectangle(fLayouter.getBorderWidth(),
390 fLayouter.getBorderWidth(), fLayouter
391 .getAnnotationSize(), fLayouter
392 .getAnnotationSize());
393 if (fAnnotationAccessExtension != null)
394 fAnnotationAccessExtension.paint(a, e.gc, can, rect);
400 * Our own private hover manager used to shop per-item pop-ups.
402 private final class HoverManager extends AbstractInformationControlManager {
407 public HoverManager() {
408 super(new IInformationControlCreator() {
409 public IInformationControl createInformationControl(Shell parent) {
410 return new DefaultInformationControl(parent);
415 setAnchor(ANCHOR_BOTTOM);
416 setFallbackAnchors(new Anchor[] { ANCHOR_BOTTOM, ANCHOR_LEFT,
421 * @see org.eclipse.jface.text.AbstractInformationControlManager#computeInformation()
423 protected void computeInformation() {
424 if (fSelection != null) {
425 Rectangle subjectArea = fSelection.canvas.getBounds();
426 Annotation annotation = fSelection.fAnnotation;
428 if (annotation != null)
429 msg = annotation.getText();
433 setInformation(msg, subjectArea);
440 protected AnnotationHoverInput fInput;
442 /** The control's shell */
443 private Shell fShell;
445 /** The composite combining all the items. */
446 protected Composite fComposite;
448 /** The hand cursor. */
449 private Cursor fHandCursor;
451 /** The currently selected item, or <code>null</code> if none is selected. */
452 private Item fSelection;
454 /** The hover manager for the per-item hovers. */
455 private HoverManager fHoverManager;
457 /** The annotation access extension. */
458 private IAnnotationAccessExtension fAnnotationAccessExtension;
460 /* listener legion */
461 private final MyPaintListener fPaintListener;
463 private final MyMouseTrackListener fMouseTrackListener;
465 private final MyMouseListener fMouseListener;
467 private final MyMenuDetectListener fMenuDetectListener;
469 private final DisposeListener fDisposeListener;
471 private final IViewportListener fViewportListener;
473 private LinearLayouter fLayouter;
476 * Creates a new control.
482 public AnnotationExpansionControl(Shell parent, int shellStyle,
483 IAnnotationAccess access) {
484 fPaintListener = new MyPaintListener();
485 fMouseTrackListener = new MyMouseTrackListener();
486 fMouseListener = new MyMouseListener();
487 fMenuDetectListener = new MyMenuDetectListener();
488 fDisposeListener = new MyDisposeListener();
489 fViewportListener = new IViewportListener() {
491 public void viewportChanged(int verticalOffset) {
496 fLayouter = new LinearLayouter();
498 if (access instanceof IAnnotationAccessExtension)
499 fAnnotationAccessExtension = (IAnnotationAccessExtension) access;
501 fShell = new Shell(parent, shellStyle | SWT.NO_FOCUS | SWT.ON_TOP);
502 Display display = fShell.getDisplay();
503 fShell.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
504 fComposite = new Composite(fShell, SWT.NO_FOCUS | SWT.NO_REDRAW_RESIZE
506 // fComposite= new Composite(fShell, SWT.NO_FOCUS | SWT.NO_REDRAW_RESIZE
507 // | SWT.NO_TRIM | SWT.V_SCROLL);
509 GridLayout layout = new GridLayout(1, true);
510 layout.marginHeight = 0;
511 layout.marginWidth = 0;
512 fShell.setLayout(layout);
514 GridData data = new GridData(GridData.FILL_BOTH);
515 data.heightHint = fLayouter.getAnnotationSize() + 2
516 * fLayouter.getBorderWidth() + 4;
517 fComposite.setLayoutData(data);
518 fComposite.addMouseTrackListener(new MouseTrackAdapter() {
520 public void mouseExit(MouseEvent e) {
521 if (fComposite == null)
523 Control[] children = fComposite.getChildren();
524 Rectangle bounds = null;
525 for (int i = 0; i < children.length; i++) {
527 bounds = children[i].getBounds();
529 bounds.add(children[i].getBounds());
530 if (bounds.contains(e.x, e.y))
534 // if none of the children contains the event, we leave the
541 // fComposite.getVerticalBar().addListener(SWT.Selection, new Listener()
544 // public void handleEvent(Event event) {
545 // Rectangle bounds= fShell.getBounds();
546 // int x= bounds.x - fLayouter.getAnnotationSize() -
547 // fLayouter.getBorderWidth();
549 // fShell.setBounds(x, y, bounds.width, bounds.height);
554 fHandCursor = new Cursor(display, SWT.CURSOR_HAND);
555 fShell.setCursor(fHandCursor);
556 fComposite.setCursor(fHandCursor);
558 setInfoSystemColor();
561 private void setInfoSystemColor() {
562 Display display = fShell.getDisplay();
563 setForegroundColor(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
564 setBackgroundColor(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
568 * @see org.eclipse.jface.text.IInformationControl#setInformation(java.lang.String)
570 public void setInformation(String information) {
575 * @see org.eclipse.jface.text.IInformationControlExtension2#setInput(java.lang.Object)
577 public void setInput(Object input) {
578 if (fInput != null && fInput.fViewer != null)
579 fInput.fViewer.removeViewportListener(fViewportListener);
581 if (input instanceof AnnotationHoverInput)
582 fInput = (AnnotationHoverInput) input;
586 inputChanged(fInput, null);
589 protected void inputChanged(Object newInput, Object newSelection) {
593 protected void refresh() {
599 if (fInput.fAnnotations == null)
602 if (fInput.fViewer != null)
603 fInput.fViewer.addViewportListener(fViewportListener);
605 fShell.setRegion(fLayouter.getShellRegion(fInput.fAnnotations.length));
607 Layout layout = fLayouter.getLayout(fInput.fAnnotations.length);
608 fComposite.setLayout(layout);
610 Control[] children = fComposite.getChildren();
611 for (int i = 0; i < fInput.fAnnotations.length; i++) {
612 Canvas canvas = (Canvas) children[i];
613 Item item = new Item();
614 item.canvas = canvas;
615 item.fAnnotation = fInput.fAnnotations[i];
616 canvas.setData(item);
622 protected void adjustItemNumber() {
623 if (fComposite == null)
626 Control[] children = fComposite.getChildren();
627 int oldSize = children.length;
628 int newSize = fInput == null ? 0 : fInput.fAnnotations.length;
630 Display display = fShell.getDisplay();
633 for (int i = oldSize; i < newSize; i++) {
634 Canvas canvas = new Canvas(fComposite, SWT.NONE);
635 Object gridData = fLayouter.getLayoutData();
636 canvas.setLayoutData(gridData);
637 canvas.setBackground(display
638 .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
640 canvas.addPaintListener(fPaintListener);
642 canvas.addMouseTrackListener(fMouseTrackListener);
644 canvas.addMouseListener(fMouseListener);
646 canvas.addListener(SWT.MenuDetect, fMenuDetectListener);
648 canvas.addDisposeListener(fDisposeListener);
651 // dispose of exceeding resources
652 for (int i = oldSize; i > newSize; i--) {
653 Item item = (Item) children[i - 1].getData();
655 children[i - 1].dispose();
661 * @see IInformationControl#setVisible(boolean)
663 public void setVisible(boolean visible) {
664 fShell.setVisible(visible);
668 * @see IInformationControl#dispose()
670 public void dispose() {
671 if (fShell != null) {
672 if (!fShell.isDisposed())
676 if (fHandCursor != null)
677 fHandCursor.dispose();
679 if (fHoverManager != null)
680 fHoverManager.dispose();
681 fHoverManager = null;
687 * @see org.eclipse.jface.text.IInformationControlExtension#hasContents()
689 public boolean hasContents() {
690 return fInput.fAnnotations != null && fInput.fAnnotations.length > 0;
694 * @see org.eclipse.jface.text.IInformationControl#setSizeConstraints(int,
697 public void setSizeConstraints(int maxWidth, int maxHeight) {
698 // fMaxWidth= maxWidth;
699 // fMaxHeight= maxHeight;
703 * @see org.eclipse.jface.text.IInformationControl#computeSizeHint()
705 public Point computeSizeHint() {
706 return fShell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
710 * @see IInformationControl#setLocation(Point)
712 public void setLocation(Point location) {
713 fShell.setLocation(location);
717 * @see IInformationControl#setSize(int, int)
719 public void setSize(int width, int height) {
720 fShell.setSize(width, height);
724 * @see IInformationControl#addDisposeListener(DisposeListener)
726 public void addDisposeListener(DisposeListener listener) {
727 fShell.addDisposeListener(listener);
731 * @see IInformationControl#removeDisposeListener(DisposeListener)
733 public void removeDisposeListener(DisposeListener listener) {
734 fShell.removeDisposeListener(listener);
738 * @see IInformationControl#setForegroundColor(Color)
740 public void setForegroundColor(Color foreground) {
741 fComposite.setForeground(foreground);
745 * @see IInformationControl#setBackgroundColor(Color)
747 public void setBackgroundColor(Color background) {
748 fComposite.setBackground(background);
752 * @see IInformationControl#isFocusControl()
754 public boolean isFocusControl() {
755 if (fComposite.isFocusControl())
758 Control[] children = fComposite.getChildren();
759 for (int i = 0; i < children.length; i++) {
760 if (children[i].isFocusControl())
767 * @see IInformationControl#setFocus()
769 public void setFocus() {
774 * @see IInformationControl#addFocusListener(FocusListener)
776 public void addFocusListener(FocusListener listener) {
777 fShell.addFocusListener(listener);
781 * @see IInformationControl#removeFocusListener(FocusListener)
783 public void removeFocusListener(FocusListener listener) {
784 fShell.removeFocusListener(listener);
787 private StyleRange[] setViewerBackground(Annotation annotation) {
788 StyledText text = fInput.fViewer.getTextWidget();
789 if (text == null || text.isDisposed())
792 Display disp = text.getDisplay();
794 Position pos = fInput.model.getPosition(annotation);
798 IRegion region = ((TextViewer) fInput.fViewer)
799 .modelRange2WidgetRange(new Region(pos.offset, pos.length));
803 StyleRange[] ranges = text.getStyleRanges(region.getOffset(), region
806 List undoRanges = new ArrayList(ranges.length);
807 for (int i = 0; i < ranges.length; i++) {
808 undoRanges.add(ranges[i].clone());
811 int offset = region.getOffset();
812 StyleRange current = undoRanges.size() > 0 ? (StyleRange) undoRanges
814 int curStart = current != null ? current.start : region.getOffset()
815 + region.getLength();
816 int curEnd = current != null ? current.start + current.length : -1;
819 // fill no-style regions
820 while (curEnd < region.getOffset() + region.getLength()) {
822 if (curStart > offset) {
823 StyleRange undoRange = new StyleRange(offset,
824 curStart - offset, null, null);
825 undoRanges.add(index, undoRange);
831 if (index < undoRanges.size()) {
833 current = (StyleRange) undoRanges.get(index);
834 curStart = current.start;
835 curEnd = current.start + current.length;
836 } else if (index == undoRanges.size()) {
840 curStart = region.getOffset() + region.getLength();
843 curEnd = region.getOffset() + region.getLength();
846 // create modified styles (with background)
847 List shadedRanges = new ArrayList(undoRanges.size());
848 for (Iterator it = undoRanges.iterator(); it.hasNext();) {
849 StyleRange range = (StyleRange) ((StyleRange) it.next()).clone();
850 shadedRanges.add(range);
851 range.background = getHighlightColor(disp);
854 // set the ranges one by one
855 for (Iterator iter = shadedRanges.iterator(); iter.hasNext();) {
856 text.setStyleRange((StyleRange) iter.next());
860 return (StyleRange[]) undoRanges.toArray(undoRanges
861 .toArray(new StyleRange[0]));
864 private void resetViewerBackground(StyleRange[] oldRanges) {
866 if (oldRanges == null)
872 StyledText text = fInput.fViewer.getTextWidget();
873 if (text == null || text.isDisposed())
876 // set the ranges one by one
877 for (int i = 0; i < oldRanges.length; i++) {
878 text.setStyleRange(oldRanges[i]);
882 private Color getHighlightColor(Display disp) {
883 return disp.getSystemColor(SWT.COLOR_GRAY);
886 private Color getSelectionColor(Display disp) {
887 return disp.getSystemColor(SWT.COLOR_GRAY);