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.swt.SWT;
18 import org.eclipse.swt.custom.StyleRange;
19 import org.eclipse.swt.custom.StyledText;
20 import org.eclipse.swt.events.DisposeEvent;
21 import org.eclipse.swt.events.DisposeListener;
22 import org.eclipse.swt.events.FocusListener;
23 import org.eclipse.swt.events.MenuEvent;
24 import org.eclipse.swt.events.MenuListener;
25 import org.eclipse.swt.events.MouseAdapter;
26 import org.eclipse.swt.events.MouseEvent;
27 import org.eclipse.swt.events.MouseTrackAdapter;
28 import org.eclipse.swt.events.MouseTrackListener;
29 import org.eclipse.swt.events.PaintEvent;
30 import org.eclipse.swt.events.PaintListener;
31 import org.eclipse.swt.graphics.Color;
32 import org.eclipse.swt.graphics.Cursor;
33 import org.eclipse.swt.graphics.Point;
34 import org.eclipse.swt.graphics.Rectangle;
35 import org.eclipse.swt.layout.GridData;
36 import org.eclipse.swt.layout.GridLayout;
37 import org.eclipse.swt.widgets.Canvas;
38 import org.eclipse.swt.widgets.Composite;
39 import org.eclipse.swt.widgets.Control;
40 import org.eclipse.swt.widgets.Display;
41 import org.eclipse.swt.widgets.Event;
42 import org.eclipse.swt.widgets.Layout;
43 import org.eclipse.swt.widgets.Listener;
44 import org.eclipse.swt.widgets.Menu;
45 import org.eclipse.swt.widgets.Shell;
46 import org.eclipse.swt.widgets.Widget;
48 import org.eclipse.jface.viewers.IDoubleClickListener;
50 import org.eclipse.jface.text.AbstractInformationControlManager;
51 import org.eclipse.jface.text.DefaultInformationControl;
52 import org.eclipse.jface.text.IInformationControl;
53 import org.eclipse.jface.text.IInformationControlCreator;
54 import org.eclipse.jface.text.IInformationControlExtension;
55 import org.eclipse.jface.text.IInformationControlExtension2;
56 import org.eclipse.jface.text.IRegion;
57 import org.eclipse.jface.text.IViewportListener;
58 import org.eclipse.jface.text.Position;
59 import org.eclipse.jface.text.Region;
60 import org.eclipse.jface.text.TextViewer;
61 import org.eclipse.jface.text.source.Annotation;
62 import org.eclipse.jface.text.source.IAnnotationAccess;
63 import org.eclipse.jface.text.source.IAnnotationAccessExtension;
64 import org.eclipse.jface.text.source.IAnnotationModel;
65 import org.eclipse.jface.text.source.ISourceViewer;
66 import org.eclipse.jface.text.source.IVerticalRulerInfo;
67 import org.eclipse.jface.text.source.IVerticalRulerListener;
68 import org.eclipse.jface.text.source.VerticalRulerEvent;
72 * A control that can display a number of annotations. The control can decide how it layouts the
73 * annotations to present them to the user.
75 * This class got moved here form Platform Text since it was not used there
76 * and caused discouraged access warnings. It will be moved down again once
77 * annotation roll-over support is provided by Platform Text.
79 * <p>Each annotation can have its custom context menu and hover.</p>
83 public class AnnotationExpansionControl implements IInformationControl, IInformationControlExtension, IInformationControlExtension2 {
86 public interface ICallback {
87 void run(IInformationControlExtension2 control);
91 * Input used by the control to display the annotations.
92 * TODO move to top-level class
93 * TODO encapsulate fields
97 public static class AnnotationHoverInput {
98 public Annotation[] fAnnotations;
99 public ISourceViewer fViewer;
100 public IVerticalRulerInfo fRulerInfo;
101 public IVerticalRulerListener fAnnotationListener;
102 public IDoubleClickListener fDoubleClickListener;
103 public ICallback redoAction;
104 public IAnnotationModel model;
107 private final class Item {
108 Annotation fAnnotation;
110 StyleRange[] oldStyles;
112 public void selected() {
113 Display disp= fShell.getDisplay();
114 canvas.setCursor(fHandCursor);
115 // TODO: shade - for now: set grey background
116 canvas.setBackground(getSelectionColor(disp));
118 // highlight the viewer background at its position
119 oldStyles= setViewerBackground(fAnnotation);
124 if (fHoverManager != null)
125 fHoverManager.showInformation();
127 if (fInput.fAnnotationListener != null) {
128 VerticalRulerEvent event= new VerticalRulerEvent(fAnnotation);
129 fInput.fAnnotationListener.annotationSelected(event);
134 public void defaultSelected() {
135 if (fInput.fAnnotationListener != null) {
136 VerticalRulerEvent event= new VerticalRulerEvent(fAnnotation);
137 fInput.fAnnotationListener.annotationDefaultSelected(event);
143 public void showContextMenu(Menu menu) {
144 if (fInput.fAnnotationListener != null) {
145 VerticalRulerEvent event= new VerticalRulerEvent(fAnnotation);
146 fInput.fAnnotationListener.annotationContextMenuAboutToShow(event, menu);
150 public void deselect() {
152 // fHoverManager.disposeInformationControl();
157 resetViewerBackground(oldStyles);
160 Display disp= fShell.getDisplay();
161 canvas.setCursor(null);
162 // TODO: remove shading - for now: set standard background
163 canvas.setBackground(disp.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
170 * Disposes of an item
172 private final static class MyDisposeListener implements DisposeListener {
174 * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
176 public void widgetDisposed(DisposeEvent e) {
177 Item item= (Item) ((Widget) e.getSource()).getData();
180 item.fAnnotation= null;
181 item.oldStyles= null;
183 ((Widget) e.getSource()).setData(null);
188 * Listener on context menu invocation on the items
190 private final class MyMenuDetectListener implements Listener {
192 * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
194 public void handleEvent(Event event) {
195 if (event.type == SWT.MenuDetect) {
196 // TODO: show per-item menu
197 // for now: show ruler context menu
198 if (fInput != null) {
199 Control ruler= fInput.fRulerInfo.getControl();
200 if (ruler != null && !ruler.isDisposed()) {
201 Menu menu= ruler.getMenu();
202 if (menu != null && !menu.isDisposed()) {
203 menu.setLocation(event.x, event.y);
204 menu.addMenuListener(new MenuListener() {
206 public void menuHidden(MenuEvent e) {
210 public void menuShown(MenuEvent e) {
214 menu.setVisible(true);
224 * Listener on mouse events on the items.
226 private final class MyMouseListener extends MouseAdapter {
228 * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
230 public void mouseDoubleClick(MouseEvent e) {
231 Item item= (Item) ((Widget) e.getSource()).getData();
232 if (e.button == 1 && item.fAnnotation == fInput.fAnnotations[0] && fInput.fDoubleClickListener != null) {
233 fInput.fDoubleClickListener.doubleClick(null);
234 // special code for JDT to renew the annotation set.
235 if (fInput.redoAction != null)
236 fInput.redoAction.run(AnnotationExpansionControl.this);
239 // TODO special action to invoke double-click action on the vertical ruler
241 // Canvas can= (Canvas) e.getSource();
242 // Annotation a= (Annotation) can.getData();
244 // a.getDoubleClickAction().run();
249 * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
251 public void mouseUp(MouseEvent e) {
252 Item item= (Item) ((Widget) e.getSource()).getData();
253 // TODO for now, to make double click work: disable single click on the first item
254 // disable later when the annotationlistener selectively handles input
255 if (item != null && e.button == 1) // && item.fAnnotation != fInput.fAnnotations[0])
256 item.defaultSelected();
260 * @see org.eclipse.swt.events.MouseAdapter#mouseDown(org.eclipse.swt.events.MouseEvent)
262 public void mouseDown(MouseEvent e) {
268 * Listener on mouse track events on the items.
270 private final class MyMouseTrackListener implements MouseTrackListener {
272 * @see org.eclipse.swt.events.MouseTrackListener#mouseEnter(org.eclipse.swt.events.MouseEvent)
274 public void mouseEnter(MouseEvent e) {
275 Item item= (Item) ((Widget) e.getSource()).getData();
281 * @see org.eclipse.swt.events.MouseTrackListener#mouseExit(org.eclipse.swt.events.MouseEvent)
283 public void mouseExit(MouseEvent e) {
285 Item item= (Item) ((Widget) e.getSource()).getData();
289 // if the event lies outside the entire popup, dispose
290 org.eclipse.swt.graphics.Region region= fShell.getRegion();
291 Canvas can= (Canvas) e.getSource();
292 Point p= can.toDisplay(e.x, e.y);
293 if (region == null) {
294 Rectangle bounds= fShell.getBounds();
295 // p= fShell.toControl(p);
296 if (!bounds.contains(p))
299 p= fShell.toControl(p);
300 if (!region.contains(p))
308 * @see org.eclipse.swt.events.MouseTrackListener#mouseHover(org.eclipse.swt.events.MouseEvent)
310 public void mouseHover(MouseEvent e) {
311 if (fHoverManager == null) {
312 fHoverManager= new HoverManager();
313 fHoverManager.takesFocusWhenVisible(false);
314 fHoverManager.install(fComposite);
315 fHoverManager.showInformation();
326 public class LinearLayouter {
328 private static final int ANNOTATION_SIZE= 14;
329 private static final int BORDER_WIDTH= 2;
331 public Layout getLayout(int itemCount) {
332 // simple layout: a row of items
333 GridLayout layout= new GridLayout(itemCount, true);
334 layout.horizontalSpacing= 1;
335 layout.verticalSpacing= 0;
336 layout.marginHeight= 1;
337 layout.marginWidth= 1;
341 public Object getLayoutData() {
342 GridData gridData= new GridData(ANNOTATION_SIZE + 2 * BORDER_WIDTH, ANNOTATION_SIZE + 2 * BORDER_WIDTH);
343 gridData.horizontalAlignment= GridData.CENTER;
344 gridData.verticalAlignment= GridData.CENTER;
348 public int getAnnotationSize() {
349 return ANNOTATION_SIZE;
352 public int getBorderWidth() {
356 public org.eclipse.swt.graphics.Region getShellRegion(int itemCount) {
357 // no special region - set to null for default shell size
365 * Listener on paint events on the items. Paints the annotation image on the given <code>GC</code>.
367 private final class MyPaintListener implements PaintListener {
369 * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
371 public void paintControl(PaintEvent e) {
372 Canvas can= (Canvas) e.getSource();
373 Annotation a= ((Item) can.getData()).fAnnotation;
375 Rectangle rect= new Rectangle(fLayouter.getBorderWidth(), fLayouter.getBorderWidth(), fLayouter.getAnnotationSize(), fLayouter.getAnnotationSize());
376 if (fAnnotationAccessExtension != null)
377 fAnnotationAccessExtension.paint(a, e.gc, can, rect);
383 * Our own private hover manager used to shop per-item pop-ups.
385 private final class HoverManager extends AbstractInformationControlManager {
390 public HoverManager() {
391 super(new IInformationControlCreator() {
392 public IInformationControl createInformationControl(Shell parent) {
393 return new DefaultInformationControl(parent);
398 setAnchor(ANCHOR_BOTTOM);
399 setFallbackAnchors(new Anchor[] {ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_RIGHT} );
403 * @see org.eclipse.jface.text.AbstractInformationControlManager#computeInformation()
405 protected void computeInformation() {
406 if (fSelection != null) {
407 Rectangle subjectArea= fSelection.canvas.getBounds();
408 Annotation annotation= fSelection.fAnnotation;
410 if (annotation != null)
411 msg= annotation.getText();
415 setInformation(msg, subjectArea);
423 protected AnnotationHoverInput fInput;
424 /** The control's shell */
425 private Shell fShell;
426 /** The composite combining all the items. */
427 protected Composite fComposite;
428 /** The hand cursor. */
429 private Cursor fHandCursor;
430 /** The currently selected item, or <code>null</code> if none is selected. */
431 private Item fSelection;
432 /** The hover manager for the per-item hovers. */
433 private HoverManager fHoverManager;
434 /** The annotation access extension. */
435 private IAnnotationAccessExtension fAnnotationAccessExtension;
438 /* listener legion */
439 private final MyPaintListener fPaintListener;
440 private final MyMouseTrackListener fMouseTrackListener;
441 private final MyMouseListener fMouseListener;
442 private final MyMenuDetectListener fMenuDetectListener;
443 private final DisposeListener fDisposeListener;
444 private final IViewportListener fViewportListener;
446 private LinearLayouter fLayouter;
449 * Creates a new control.
455 public AnnotationExpansionControl(Shell parent, int shellStyle, IAnnotationAccess access) {
456 fPaintListener= new MyPaintListener();
457 fMouseTrackListener= new MyMouseTrackListener();
458 fMouseListener= new MyMouseListener();
459 fMenuDetectListener= new MyMenuDetectListener();
460 fDisposeListener= new MyDisposeListener();
461 fViewportListener= new IViewportListener() {
463 public void viewportChanged(int verticalOffset) {
468 fLayouter= new LinearLayouter();
470 if (access instanceof IAnnotationAccessExtension)
471 fAnnotationAccessExtension= (IAnnotationAccessExtension) access;
473 fShell= new Shell(parent, shellStyle | SWT.NO_FOCUS | SWT.ON_TOP);
474 Display display= fShell.getDisplay();
475 fShell.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
476 fComposite= new Composite(fShell, SWT.NO_FOCUS | SWT.NO_REDRAW_RESIZE | SWT.NO_TRIM);
477 // fComposite= new Composite(fShell, SWT.NO_FOCUS | SWT.NO_REDRAW_RESIZE | SWT.NO_TRIM | SWT.V_SCROLL);
479 GridLayout layout= new GridLayout(1, true);
480 layout.marginHeight= 0;
481 layout.marginWidth= 0;
482 fShell.setLayout(layout);
484 GridData data= new GridData(GridData.FILL_BOTH);
485 data.heightHint= fLayouter.getAnnotationSize() + 2 * fLayouter.getBorderWidth() + 4;
486 fComposite.setLayoutData(data);
487 fComposite.addMouseTrackListener(new MouseTrackAdapter() {
489 public void mouseExit(MouseEvent e) {
490 if (fComposite == null)
492 Control[] children= fComposite.getChildren();
493 Rectangle bounds= null;
494 for (int i= 0; i < children.length; i++) {
496 bounds= children[i].getBounds();
498 bounds.add(children[i].getBounds());
499 if (bounds.contains(e.x, e.y))
503 // if none of the children contains the event, we leave the popup
509 // fComposite.getVerticalBar().addListener(SWT.Selection, new Listener() {
511 // public void handleEvent(Event event) {
512 // Rectangle bounds= fShell.getBounds();
513 // int x= bounds.x - fLayouter.getAnnotationSize() - fLayouter.getBorderWidth();
515 // fShell.setBounds(x, y, bounds.width, bounds.height);
520 fHandCursor= new Cursor(display, SWT.CURSOR_HAND);
521 fShell.setCursor(fHandCursor);
522 fComposite.setCursor(fHandCursor);
524 setInfoSystemColor();
527 private void setInfoSystemColor() {
528 Display display= fShell.getDisplay();
529 setForegroundColor(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
530 setBackgroundColor(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
534 * @see org.eclipse.jface.text.IInformationControl#setInformation(java.lang.String)
536 public void setInformation(String information) {
542 * @see org.eclipse.jface.text.IInformationControlExtension2#setInput(java.lang.Object)
544 public void setInput(Object input) {
545 if (fInput != null && fInput.fViewer != null)
546 fInput.fViewer.removeViewportListener(fViewportListener);
548 if (input instanceof AnnotationHoverInput)
549 fInput= (AnnotationHoverInput) input;
553 inputChanged(fInput, null);
556 protected void inputChanged(Object newInput, Object newSelection) {
560 protected void refresh() {
566 if (fInput.fAnnotations == null)
569 if (fInput.fViewer != null)
570 fInput.fViewer.addViewportListener(fViewportListener);
572 fShell.setRegion(fLayouter.getShellRegion(fInput.fAnnotations.length));
574 Layout layout= fLayouter.getLayout(fInput.fAnnotations.length);
575 fComposite.setLayout(layout);
577 Control[] children= fComposite.getChildren();
578 for (int i= 0; i < fInput.fAnnotations.length; i++) {
579 Canvas canvas= (Canvas) children[i];
580 Item item= new Item();
582 item.fAnnotation= fInput.fAnnotations[i];
583 canvas.setData(item);
589 protected void adjustItemNumber() {
590 if (fComposite == null)
593 Control[] children= fComposite.getChildren();
594 int oldSize= children.length;
595 int newSize= fInput == null ? 0 : fInput.fAnnotations.length;
597 Display display= fShell.getDisplay();
600 for (int i= oldSize; i < newSize; i++) {
601 Canvas canvas= new Canvas(fComposite, SWT.NONE);
602 Object gridData= fLayouter.getLayoutData();
603 canvas.setLayoutData(gridData);
604 canvas.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
606 canvas.addPaintListener(fPaintListener);
608 canvas.addMouseTrackListener(fMouseTrackListener);
610 canvas.addMouseListener(fMouseListener);
612 canvas.addListener(SWT.MenuDetect, fMenuDetectListener);
614 canvas.addDisposeListener(fDisposeListener);
617 // dispose of exceeding resources
618 for (int i= oldSize; i > newSize; i--) {
619 Item item= (Item) children[i - 1].getData();
621 children[i - 1].dispose();
627 * @see IInformationControl#setVisible(boolean)
629 public void setVisible(boolean visible) {
630 fShell.setVisible(visible);
634 * @see IInformationControl#dispose()
636 public void dispose() {
637 if (fShell != null) {
638 if (!fShell.isDisposed())
642 if (fHandCursor != null)
643 fHandCursor.dispose();
645 if (fHoverManager != null)
646 fHoverManager.dispose();
653 * @see org.eclipse.jface.text.IInformationControlExtension#hasContents()
655 public boolean hasContents() {
656 return fInput.fAnnotations != null && fInput.fAnnotations.length > 0;
660 * @see org.eclipse.jface.text.IInformationControl#setSizeConstraints(int, int)
662 public void setSizeConstraints(int maxWidth, int maxHeight) {
663 //fMaxWidth= maxWidth;
664 //fMaxHeight= maxHeight;
668 * @see org.eclipse.jface.text.IInformationControl#computeSizeHint()
670 public Point computeSizeHint() {
671 return fShell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
675 * @see IInformationControl#setLocation(Point)
677 public void setLocation(Point location) {
678 fShell.setLocation(location);
682 * @see IInformationControl#setSize(int, int)
684 public void setSize(int width, int height) {
685 fShell.setSize(width, height);
689 * @see IInformationControl#addDisposeListener(DisposeListener)
691 public void addDisposeListener(DisposeListener listener) {
692 fShell.addDisposeListener(listener);
696 * @see IInformationControl#removeDisposeListener(DisposeListener)
698 public void removeDisposeListener(DisposeListener listener) {
699 fShell.removeDisposeListener(listener);
703 * @see IInformationControl#setForegroundColor(Color)
705 public void setForegroundColor(Color foreground) {
706 fComposite.setForeground(foreground);
710 * @see IInformationControl#setBackgroundColor(Color)
712 public void setBackgroundColor(Color background) {
713 fComposite.setBackground(background);
717 * @see IInformationControl#isFocusControl()
719 public boolean isFocusControl() {
720 if (fComposite.isFocusControl())
723 Control[] children= fComposite.getChildren();
724 for (int i= 0; i < children.length; i++) {
725 if (children[i].isFocusControl())
732 * @see IInformationControl#setFocus()
734 public void setFocus() {
739 * @see IInformationControl#addFocusListener(FocusListener)
741 public void addFocusListener(FocusListener listener) {
742 fShell.addFocusListener(listener);
746 * @see IInformationControl#removeFocusListener(FocusListener)
748 public void removeFocusListener(FocusListener listener) {
749 fShell.removeFocusListener(listener);
752 private StyleRange[] setViewerBackground(Annotation annotation) {
753 StyledText text= fInput.fViewer.getTextWidget();
754 if (text == null || text.isDisposed())
757 Display disp= text.getDisplay();
759 Position pos= fInput.model.getPosition(annotation);
763 IRegion region= ((TextViewer)fInput.fViewer).modelRange2WidgetRange(new Region(pos.offset, pos.length));
767 StyleRange[] ranges= text.getStyleRanges(region.getOffset(), region.getLength());
769 List undoRanges= new ArrayList(ranges.length);
770 for (int i= 0; i < ranges.length; i++) {
771 undoRanges.add(ranges[i].clone());
774 int offset= region.getOffset();
775 StyleRange current= undoRanges.size() > 0 ? (StyleRange) undoRanges.get(0) : null;
776 int curStart= current != null ? current.start : region.getOffset() + region.getLength();
777 int curEnd= current != null ? current.start + current.length : -1;
780 // fill no-style regions
781 while (curEnd < region.getOffset() + region.getLength()) {
783 if (curStart > offset) {
784 StyleRange undoRange= new StyleRange(offset, curStart - offset, null, null);
785 undoRanges.add(index, undoRange);
791 if (index < undoRanges.size()) {
793 current= (StyleRange) undoRanges.get(index);
794 curStart= current.start;
795 curEnd= current.start + current.length;
796 } else if (index == undoRanges.size()) {
800 curStart= region.getOffset() + region.getLength();
803 curEnd= region.getOffset() + region.getLength();
806 // create modified styles (with background)
807 List shadedRanges= new ArrayList(undoRanges.size());
808 for (Iterator it= undoRanges.iterator(); it.hasNext(); ) {
809 StyleRange range= (StyleRange) ((StyleRange) it.next()).clone();
810 shadedRanges.add(range);
811 range.background= getHighlightColor(disp);
814 // set the ranges one by one
815 for (Iterator iter= shadedRanges.iterator(); iter.hasNext(); ) {
816 text.setStyleRange((StyleRange) iter.next());
820 return (StyleRange[]) undoRanges.toArray(undoRanges.toArray(new StyleRange[0]));
823 private void resetViewerBackground(StyleRange[] oldRanges) {
825 if (oldRanges == null)
831 StyledText text= fInput.fViewer.getTextWidget();
832 if (text == null || text.isDisposed())
835 // set the ranges one by one
836 for (int i= 0; i < oldRanges.length; i++) {
837 text.setStyleRange(oldRanges[i]);
841 private Color getHighlightColor(Display disp) {
842 return disp.getSystemColor(SWT.COLOR_GRAY);
845 private Color getSelectionColor(Display disp) {
846 return disp.getSystemColor(SWT.COLOR_GRAY);