### Eclipse Workspace Patch 1.0 #P org.eclipse.mylyn.context.ui Index: src/org/eclipse/mylyn/internal/context/ui/Messages.java =================================================================== RCS file: /cvsroot/mylyn/org.eclipse.mylyn.contexts/org.eclipse.mylyn.context.ui/src/org/eclipse/mylyn/internal/context/ui/Messages.java,v retrieving revision 1.3 diff -u -r1.3 Messages.java --- src/org/eclipse/mylyn/internal/context/ui/Messages.java 8 Jun 2010 23:04:49 -0000 1.3 +++ src/org/eclipse/mylyn/internal/context/ui/Messages.java 14 Feb 2011 20:16:05 -0000 @@ -40,4 +40,8 @@ public static String AbstractFocusViewAction_Apply_Mylyn; public static String AbstractFocusViewAction_Empty_task_context; + + public static String FilteredChildrenDecorationDrawer_No_Filtered_Children; + + public static String FilteredChildrenDecorationDrawer_Show_Filtered_Children; } Index: src/org/eclipse/mylyn/internal/context/ui/BrowseFilteredListener.java =================================================================== RCS file: /cvsroot/mylyn/org.eclipse.mylyn.contexts/org.eclipse.mylyn.context.ui/src/org/eclipse/mylyn/internal/context/ui/BrowseFilteredListener.java,v retrieving revision 1.22 diff -u -r1.22 BrowseFilteredListener.java --- src/org/eclipse/mylyn/internal/context/ui/BrowseFilteredListener.java 14 Feb 2011 00:29:04 -0000 1.22 +++ src/org/eclipse/mylyn/internal/context/ui/BrowseFilteredListener.java 14 Feb 2011 20:16:05 -0000 @@ -42,6 +42,8 @@ private final StructuredViewer viewer; + private boolean wasExternalClick = false; + public BrowseFilteredListener(StructuredViewer viewer) { this.viewer = viewer; } @@ -78,6 +80,10 @@ } } + public void setWasExternalClick(boolean wasExternalClick) { + this.wasExternalClick = wasExternalClick; + } + public void keyPressed(KeyEvent event) { // ignore } @@ -130,8 +136,9 @@ unfilter(filter, treeViewer, selectedObject); } else { if (event.button == 1) { - if ((event.stateMask & SWT.MOD1) != 0) { + if ((event.stateMask & SWT.MOD1) != 0 || wasExternalClick) { viewer.refresh(selectedObject); + wasExternalClick = false; } else { final Object unfiltered = filter.getLastTemporarilyUnfiltered(); if (unfiltered != null) { @@ -187,4 +194,12 @@ } return null; } + + public boolean isUnfiltered(Object object) { + InterestFilter interestFilter = getInterestFilter(viewer); + if (interestFilter != null) { + return interestFilter.isTemporarilyUnfiltered(object); + } + return false; + } } Index: src/org/eclipse/mylyn/internal/context/ui/FocusedViewerManager.java =================================================================== RCS file: /cvsroot/mylyn/org.eclipse.mylyn.contexts/org.eclipse.mylyn.context.ui/src/org/eclipse/mylyn/internal/context/ui/FocusedViewerManager.java,v retrieving revision 1.49 diff -u -r1.49 FocusedViewerManager.java --- src/org/eclipse/mylyn/internal/context/ui/FocusedViewerManager.java 8 Jun 2010 23:04:49 -0000 1.49 +++ src/org/eclipse/mylyn/internal/context/ui/FocusedViewerManager.java 14 Feb 2011 20:16:05 -0000 @@ -199,9 +199,34 @@ } } + private final Map decorationMap = new HashMap(); + + private void removeFilterDecorations(StructuredViewer viewer) { + if (viewer instanceof TreeViewer) { + TreeViewer treeViewer = (TreeViewer) viewer; + + FilteredChildrenDecorationDrawer filterViewDrawer = decorationMap.remove(treeViewer); + if (filterViewDrawer != null) { + filterViewDrawer.dispose(); + } + } + } + + private void addFilterDecorations(StructuredViewer viewer, BrowseFilteredListener listener) { + if (viewer instanceof TreeViewer) { + TreeViewer treeViewer = (TreeViewer) viewer; + FilteredChildrenDecorationDrawer filteredViewDrawer = new FilteredChildrenDecorationDrawer(treeViewer, + listener); + decorationMap.put(treeViewer, filteredViewDrawer); + filteredViewDrawer.applyToTreeViewer(); + + } + } + public void removeManagedViewer(StructuredViewer viewer, IWorkbenchPart viewPart) { managedViewers.remove(viewer); partToViewerMap.remove(viewPart); + removeFilterDecorations(viewer); BrowseFilteredListener listener = listenerMap.get(viewer); if (listener != null && viewer != null && !viewer.getControl().isDisposed()) { viewer.getControl().removeMouseListener(listener); @@ -214,6 +239,10 @@ if (viewer != null && action != null) { focusActions.put(viewer, action); } + BrowseFilteredListener listener = listenerMap.get(viewer); + if (listener != null) { + addFilterDecorations(viewer, listener); + } } @Deprecated @@ -224,6 +253,7 @@ } public void removeFilteredViewer(StructuredViewer viewer) { + removeFilterDecorations(viewer); focusActions.remove(viewer); filteredViewers.remove(viewer); } @@ -368,9 +398,8 @@ if (viewer instanceof TreeViewer && filteredViewers.contains(viewer) && hasInterestFilter(viewer, true) - && ContextUiPlugin.getDefault() - .getPreferenceStore() - .getBoolean(IContextUiPreferenceContstants.AUTO_MANAGE_EXPANSION)) { + && ContextUiPlugin.getDefault().getPreferenceStore().getBoolean( + IContextUiPreferenceContstants.AUTO_MANAGE_EXPANSION)) { TreeViewer treeViewer = (TreeViewer) viewer; // HACK to fix bug 278569: [context] errors with Markers view and active Mylyn task Index: src/org/eclipse/mylyn/internal/context/ui/messages.properties =================================================================== RCS file: /cvsroot/mylyn/org.eclipse.mylyn.contexts/org.eclipse.mylyn.context.ui/src/org/eclipse/mylyn/internal/context/ui/messages.properties,v retrieving revision 1.3 diff -u -r1.3 messages.properties --- src/org/eclipse/mylyn/internal/context/ui/messages.properties 17 Sep 2009 02:16:04 -0000 1.3 +++ src/org/eclipse/mylyn/internal/context/ui/messages.properties 14 Feb 2011 20:16:05 -0000 @@ -18,3 +18,5 @@ AbstractFocusViewAction_Apply_Mylyn=Apply Mylyn AbstractFocusViewAction_Empty_task_context=Empty task context, unfocus or Alt+click +FilteredChildrenDecorationDrawer_No_Filtered_Children=No Filtered Children +FilteredChildrenDecorationDrawer_Show_Filtered_Children=Show Filtered Children (Alt+click) Index: src/org/eclipse/mylyn/context/ui/InterestFilter.java =================================================================== RCS file: /cvsroot/mylyn/org.eclipse.mylyn.contexts/org.eclipse.mylyn.context.ui/src/org/eclipse/mylyn/context/ui/InterestFilter.java,v retrieving revision 1.37 diff -u -r1.37 InterestFilter.java --- src/org/eclipse/mylyn/context/ui/InterestFilter.java 14 Feb 2011 00:29:04 -0000 1.37 +++ src/org/eclipse/mylyn/context/ui/InterestFilter.java 14 Feb 2011 20:16:05 -0000 @@ -144,7 +144,10 @@ // } } - private boolean isTemporarilyUnfiltered(Object parent) { + /** + * @since 3.5 + */ + public boolean isTemporarilyUnfiltered(Object parent) { if (parent instanceof TreePath) { TreePath treePath = (TreePath) parent; parent = treePath.getLastSegment(); Index: src/org/eclipse/mylyn/internal/context/ui/FilteredChildrenDecorationDrawer.java =================================================================== RCS file: src/org/eclipse/mylyn/internal/context/ui/FilteredChildrenDecorationDrawer.java diff -N src/org/eclipse/mylyn/internal/context/ui/FilteredChildrenDecorationDrawer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/mylyn/internal/context/ui/FilteredChildrenDecorationDrawer.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,289 @@ +package org.eclipse.mylyn.internal.context.ui; + +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.mylyn.internal.provisional.commons.ui.CommonImages; +import org.eclipse.mylyn.internal.provisional.commons.ui.WorkbenchUtil; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ToolTip; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +/** + * @author Mik Kersten + */ + +public class FilteredChildrenDecorationDrawer implements Listener { + + private final class MoveListener implements MouseMoveListener, MouseListener, MouseTrackListener { + + private TreeItem lastItem; + + private final TreeViewer viewer; + + private final BrowseFilteredListener browseFilteredListener; + + private final ToolTip toolTip; + + public MoveListener(TreeViewer viewer, BrowseFilteredListener browseFilteredListener) { + this.viewer = viewer; + this.browseFilteredListener = browseFilteredListener; + toolTip = new ToolTip(WorkbenchUtil.getShell(), SWT.NONE); + } + + public void mouseEnter(MouseEvent e) { + mouseMove(e); + } + + public void mouseExit(MouseEvent e) { + if (lastItem != null && !lastItem.isDisposed()) { + lastItem.setData(ID_HOVER, NodeState.LESS); + } + if (toolTip != null && !toolTip.isDisposed()) { + toolTip.setVisible(false); + } + + lastItem = null; + redrawTree(); + } + + private void redrawTree() { + if (viewer.getTree() != null && !viewer.getTree().isDisposed()) { + viewer.getTree().redraw(); + } + } + + public void mouseHover(MouseEvent e) { + + if (toolTip != null && !toolTip.isDisposed()) { + Tree tree = (Tree) e.widget; + TreeItem item = findItem(tree, e.y); + if (item != null && !item.isDisposed()) { + Object data = item.getData(ID_HOVER); + if (data == NodeState.MORE) { + toolTip.setText(Messages.FilteredChildrenDecorationDrawer_Show_Filtered_Children); + } else { + toolTip.setText(Messages.FilteredChildrenDecorationDrawer_No_Filtered_Children); + } + if (inImageBounds(tree, item, e)) { + toolTip.setVisible(true); + } + } + } + } + + private boolean inImageBounds(Tree tree, TreeItem item, MouseEvent e) { + boolean shouldUnfilter = false; + int selectedX = e.x; + + int imageStartX = item.getBounds().x + item.getBounds().width + IMAGE_PADDING; + + Rectangle clientArea = tree.getClientArea(); + int currentTreeBounds = clientArea.x + clientArea.width; + if (imageStartX > currentTreeBounds) { + imageStartX = currentTreeBounds - moreImage.getBounds().width; + } + + int imageEndX = imageStartX + moreImage.getBounds().width; + + return selectedX > imageStartX && selectedX < imageEndX; + } + + public void mouseMove(MouseEvent e) { + if (toolTip != null && !toolTip.isDisposed()) { + toolTip.setVisible(false); + } + + if (!(e.widget instanceof Tree) || e.widget.isDisposed()) { + return; + } + Tree tree = (Tree) e.widget; + TreeItem item = findItem(tree, e.y); + if (item != null && !item.isDisposed()) { + if (lastItem != null && !lastItem.isDisposed() && !lastItem.equals(item)) { + lastItem.setData(ID_HOVER, NodeState.LESS); + } + + Object data = item.getData(ID_HAS_CHILDREN); + if (data == null || data == Boolean.TRUE) { + item.setData(ID_HOVER, NodeState.MORE); + } else { + item.setData(ID_HOVER, NodeState.LESS); + } + if (lastItem == null || (!lastItem.isDisposed() && !lastItem.equals(item))) { + redrawTree(); + } + lastItem = item; + } else { + if (lastItem != null && !lastItem.isDisposed() && !lastItem.equals(item)) { + lastItem.setData(ID_HOVER, NodeState.LESS); + redrawTree(); + } + lastItem = item; + } + } + + public void dispose() { + if (toolTip != null && !toolTip.isDisposed()) { + toolTip.dispose(); + } + } + + public void mouseDoubleClick(MouseEvent e) { + // ignore + + } + + public void mouseDown(MouseEvent e) { + if (toolTip != null && !toolTip.isDisposed()) { + toolTip.setVisible(false); + } + + if (!(e.widget instanceof Tree) || e.widget.isDisposed()) { + // we only handle tree's + return; + } + + Tree tree = (Tree) e.widget; + TreeItem item = findItem(tree, e.y); + + if (item == null || item.isDisposed()) { + // we can't do anything if we cant find the tree items + return; + } + + int prevNumberItems = item.getItemCount(); + boolean wasUnfiltered = browseFilteredListener.isUnfiltered(item.getData()); + if (inImageBounds(tree, item, e)) { + browseFilteredListener.setWasExternalClick(true); + browseFilteredListener.unfilterSelection(viewer, new StructuredSelection(item.getData())); + } + int newNumItems = item.getItemCount(); + if (newNumItems == prevNumberItems && !wasUnfiltered) { + item.setData(ID_HOVER, NodeState.MORE_ERROR); + item.setData(ID_HAS_CHILDREN, Boolean.FALSE); + redrawTree(); + } + } + + public void mouseUp(MouseEvent e) { + // ignore + + } + } + + private static final int SCROLL_BAR_OFFSET = 15; + + private static final int IMAGE_PADDING = 5; + + // XXX Update Images + private final Image moreImage = CommonImages.getImage(CommonImages.EXPAND_ALL); + + private final Image moreErrorImage = CommonImages.getImage(CommonImages.REMOVE); + + enum NodeState { + MORE, LESS, MORE_ERROR + }; + + private static final String ID_HOVER = "mylyn-context-hover"; //$NON-NLS-1$ + + private static final String ID_HAS_CHILDREN = "mylyn-context-has-children"; //$NON-NLS-1$ + + private final TreeViewer treeViewer; + + private MoveListener listener; + + private final BrowseFilteredListener browseFilteredListener; + + public FilteredChildrenDecorationDrawer(TreeViewer treeViewer, BrowseFilteredListener browseFilteredListener) { + this.treeViewer = treeViewer; + this.browseFilteredListener = browseFilteredListener; + } + + public void applyToTreeViewer() { + if (treeViewer.getTree() != null && !treeViewer.getTree().isDisposed()) { + treeViewer.getTree().addListener(SWT.PaintItem, this); + + listener = new MoveListener(treeViewer, browseFilteredListener); + treeViewer.getTree().addMouseMoveListener(listener); + treeViewer.getTree().addMouseListener(listener); + treeViewer.getTree().addMouseTrackListener(listener); + } + } + + public void dispose() { + if (treeViewer.getTree() == null || treeViewer.getTree().isDisposed()) { + return; + } + treeViewer.getTree().removeListener(SWT.PaintItem, this); + + if (listener != null) { + treeViewer.getTree().removeMouseMoveListener(listener); + treeViewer.getTree().removeMouseListener(listener); + treeViewer.getTree().removeMouseTrackListener(listener); + listener.dispose(); + } + } + + /* + * NOTE: MeasureItem, PaintItem and EraseItem are called repeatedly. + * Therefore, it is critical for performance that these methods be as + * efficient as possible. + */ + public void handleEvent(Event event) { + + if (!(event.widget instanceof Tree)) { + // we only handle tree's + return; + } + + switch (event.type) { + case SWT.PaintItem: { + Tree tree = (Tree) event.widget; + if (tree.isDisposed()) { + return; + } + TreeItem item = findItem(tree, event.y); + if (item == null || item.isDisposed()) { + return; + } + + int imageStartX = event.x + event.width + IMAGE_PADDING; + + Rectangle clientArea = tree.getClientArea(); + int currentTreeBounds = clientArea.x + clientArea.width; + if (imageStartX > currentTreeBounds) { + imageStartX = currentTreeBounds - moreImage.getBounds().width; + } + + NodeState value = (NodeState) item.getData(ID_HOVER); + if (value != null && value.equals(NodeState.MORE)) { + event.gc.drawImage(moreImage, imageStartX, event.y); + } else if (value != null && value.equals(NodeState.MORE_ERROR)) { + event.gc.drawImage(moreErrorImage, imageStartX, event.y); + } + break; + } + } + } + + private TreeItem findItem(Tree tree, int y) { + TreeItem item = null; + Point size = tree.getSize(); + final int RATE = 17; + for (int i = 0; i <= RATE && item == null; i++) { + int position = size.x / RATE + (i * size.x / RATE); + item = tree.getItem(new Point(position, y)); + } + return item; + } +}