| /******************************************************************************* |
| * Copyright (c) 2004, 2013 Tasktop Technologies 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: |
| * Tasktop Technologies - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.context.ui; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.viewers.AbstractTreeViewer; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ISelectionProvider; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.StructuredViewer; |
| import org.eclipse.jface.viewers.TreeViewer; |
| import org.eclipse.jface.viewers.ViewerFilter; |
| import org.eclipse.mylyn.commons.core.StatusHandler; |
| import org.eclipse.mylyn.commons.workbench.DelayedRefreshJob; |
| import org.eclipse.mylyn.context.core.AbstractContextListener; |
| import org.eclipse.mylyn.context.core.AbstractContextStructureBridge; |
| import org.eclipse.mylyn.context.core.ContextChangeEvent; |
| import org.eclipse.mylyn.context.core.ContextCore; |
| import org.eclipse.mylyn.context.core.IInteractionElement; |
| import org.eclipse.mylyn.context.ui.AbstractFocusViewAction; |
| import org.eclipse.mylyn.context.ui.InterestFilter; |
| import org.eclipse.mylyn.internal.context.core.ContextCorePlugin; |
| import org.eclipse.ui.ISelectionListener; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.PlatformUI; |
| |
| /** |
| * Encapsulates the element refresh and expansion state policy for all viewers focused on context. |
| * |
| * @author Mik Kersten |
| * @author Shawn Minto |
| */ |
| public class FocusedViewerManager extends AbstractContextListener implements ISelectionListener { |
| |
| private final CopyOnWriteArrayList<StructuredViewer> managedViewers = new CopyOnWriteArrayList<StructuredViewer>(); |
| |
| private final CopyOnWriteArrayList<StructuredViewer> filteredViewers = new CopyOnWriteArrayList<StructuredViewer>(); |
| |
| private final Map<StructuredViewer, AbstractFocusViewAction> focusActions = new HashMap<StructuredViewer, AbstractFocusViewAction>(); |
| |
| private final Map<StructuredViewer, BrowseFilteredListener> listenerMap = new HashMap<StructuredViewer, BrowseFilteredListener>(); |
| |
| private final Map<IWorkbenchPart, StructuredViewer> partToViewerMap = new HashMap<IWorkbenchPart, StructuredViewer>(); |
| |
| private final Map<StructuredViewer, FocusedViewerDelayedRefreshJob> fullRefreshJobs = new HashMap<StructuredViewer, FocusedViewerDelayedRefreshJob>(); |
| |
| // TODO: consider merging in order to discard minors when majors come in, see bug 209846 |
| private final Map<StructuredViewer, FocusedViewerDelayedRefreshJob> minorRefreshJobs = new HashMap<StructuredViewer, FocusedViewerDelayedRefreshJob>(); |
| |
| private class FocusedViewerDelayedRefreshJob extends DelayedRefreshJob { |
| |
| private boolean minor = false; |
| |
| private boolean updateExpansion; |
| |
| public FocusedViewerDelayedRefreshJob(StructuredViewer viewer, String name, boolean minor) { |
| super(viewer, name); |
| this.minor = minor; |
| |
| } |
| |
| @Override |
| protected void doRefresh(Object[] items) { |
| try { |
| if (viewer == null) { |
| return; |
| } else if (viewer.getControl().isDisposed()) { |
| managedViewers.remove(viewer); |
| } else { |
| if (items == null || items.length == 0) { |
| if (!minor) { |
| viewer.refresh(false); |
| if (updateExpansion) { |
| FocusedViewerManager.this.updateExpansionState(viewer, null); |
| } |
| } else { |
| try { |
| viewer.getControl().setRedraw(false); |
| viewer.refresh(true); |
| if (updateExpansion) { |
| FocusedViewerManager.this.updateExpansionState(viewer, null); |
| } |
| } finally { |
| viewer.getControl().setRedraw(true); |
| } |
| } |
| } else { |
| if (filteredViewers.contains(viewer)) { |
| try { |
| viewer.getControl().setRedraw(false); |
| viewer.refresh(minor); |
| if (updateExpansion) { |
| FocusedViewerManager.this.updateExpansionState(viewer, null); |
| } |
| } finally { |
| viewer.getControl().setRedraw(true); |
| } |
| } else { // don't need to worry about content changes |
| try { |
| viewer.getControl().setRedraw(false); |
| for (Object item : items) { |
| Object objectToRefresh = item; |
| if (item instanceof IInteractionElement) { |
| IInteractionElement node = (IInteractionElement) item; |
| AbstractContextStructureBridge structureBridge = ContextCorePlugin.getDefault() |
| .getStructureBridge(node.getContentType()); |
| objectToRefresh = structureBridge.getObjectForHandle(node.getHandleIdentifier()); |
| } |
| if (objectToRefresh != null) { |
| viewer.update(objectToRefresh, null); |
| if (updateExpansion) { |
| FocusedViewerManager.this.updateExpansionState(viewer, objectToRefresh); |
| } |
| } |
| } |
| } finally { |
| viewer.getControl().setRedraw(true); |
| } |
| } |
| } |
| } |
| } finally { |
| updateExpansion = false; |
| } |
| |
| } |
| |
| public void refreshElements(Object[] elements, boolean updateExpansion) { |
| this.updateExpansion |= updateExpansion; |
| super.refreshElements(elements); |
| } |
| |
| @Override |
| public void refreshElements(Object[] elements) { |
| refreshElements(elements, true); |
| } |
| } |
| |
| /** |
| * For testing. |
| */ |
| private boolean syncRefreshMode = false; |
| |
| // private boolean internalExpandExceptionLogged; |
| |
| public FocusedViewerManager() { |
| // NOTE: no longer using viewer part tracker due to bug 162346 |
| // VIEWER_PART_TRACKER.install(PlatformUI.getWorkbench()); |
| } |
| |
| public void dispose() { |
| // VIEWER_PART_TRACKER.dispose(PlatformUI.getWorkbench()); |
| } |
| |
| public void selectionChanged(IWorkbenchPart part, ISelection selection) { |
| // ignore |
| } |
| |
| public void addManagedViewer(StructuredViewer viewer, IWorkbenchPart viewPart) { |
| if (viewer != null && !managedViewers.contains(viewer)) { |
| managedViewers.add(viewer); |
| partToViewerMap.put(viewPart, viewer); |
| BrowseFilteredListener listener = new BrowseFilteredListener(viewer); |
| listenerMap.put(viewer, listener); |
| viewer.getControl().addMouseListener(listener); |
| viewer.getControl().addKeyListener(listener); |
| |
| try { |
| // NOTE: this needs to be done because some views (e.g. Project Explorer) are not |
| // correctly initialized on startup and do not have the dummy selection event |
| // sent to them. See PartPluginAction and bug 213545. |
| // TODO consider a mechanism to identify only views that provide focus |
| FocusedViewerManager.initializeViewerSelection(viewPart); |
| Set<IInteractionElement> emptySet = Collections.emptySet(); |
| refreshViewer(emptySet, true, viewer, true); |
| } catch (Exception e) { |
| StatusHandler.log(new Status(IStatus.ERROR, ContextUiPlugin.ID_PLUGIN, |
| "Could not initialize focused viewer", e)); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| private final Map<TreeViewer, FilteredChildrenDecorationDrawer> decorationMap = new HashMap<TreeViewer, FilteredChildrenDecorationDrawer>(); |
| |
| 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); |
| if (filteredViewDrawer.applyToTreeViewer()) { |
| decorationMap.put(treeViewer, filteredViewDrawer); |
| } |
| |
| } |
| } |
| |
| 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); |
| viewer.getControl().removeKeyListener(listener); |
| } |
| } |
| |
| public void addFilteredViewer(StructuredViewer viewer, AbstractFocusViewAction action) { |
| addFilteredViewer(viewer); |
| if (viewer != null && action != null) { |
| focusActions.put(viewer, action); |
| } |
| BrowseFilteredListener listener = listenerMap.get(viewer); |
| if (listener != null) { |
| addFilterDecorations(viewer, listener); |
| } |
| } |
| |
| @Deprecated |
| public void addFilteredViewer(StructuredViewer viewer) { |
| if (viewer != null && !filteredViewers.contains(viewer)) { |
| filteredViewers.add(viewer); |
| } |
| } |
| |
| public void removeFilteredViewer(StructuredViewer viewer) { |
| removeFilterDecorations(viewer); |
| focusActions.remove(viewer); |
| filteredViewers.remove(viewer); |
| } |
| |
| @Override |
| public void contextChanged(ContextChangeEvent event) { |
| switch (event.getEventKind()) { |
| case ACTIVATED: |
| refreshViewers(); |
| break; |
| case DEACTIVATED: |
| refreshViewers(); |
| for (StructuredViewer structuredViewer : managedViewers) { |
| if (structuredViewer instanceof TreeViewer) { |
| ((TreeViewer) structuredViewer).collapseAll(); |
| } |
| } |
| break; |
| case CLEARED: |
| if (event.isActiveContext()) { |
| // ensure we dont refresh the viewers if a context other than the active one is deleted or cleared |
| // bug #265688 |
| refreshViewers(); |
| for (StructuredViewer structuredViewer : managedViewers) { |
| if (structuredViewer instanceof TreeViewer) { |
| ((TreeViewer) structuredViewer).collapseAll(); |
| } |
| } |
| } |
| break; |
| case INTEREST_CHANGED: |
| if (event.isActiveContext()) { |
| refreshViewers(event.getElements(), false, true); |
| } |
| break; |
| case LANDMARKS_ADDED: |
| if (event.isActiveContext()) { |
| refreshViewers(event.getElements(), true, false); |
| } |
| break; |
| case LANDMARKS_REMOVED: |
| if (event.isActiveContext()) { |
| refreshViewers(event.getElements(), true, false); |
| } |
| break; |
| case ELEMENTS_DELETED: |
| if (event.isActiveContext()) { |
| /* |
| * TODO: consider making this work per-element and parent |
| * Should we collect all parents before calling refresh? |
| */ |
| ArrayList<IInteractionElement> toRefresh = new ArrayList<IInteractionElement>(); |
| for (IInteractionElement interactionElement : event.getElements()) { |
| AbstractContextStructureBridge structureBridge = ContextCore.getStructureBridge(interactionElement.getContentType()); |
| IInteractionElement parent = ContextCore.getContextManager().getElement( |
| structureBridge.getParentHandle(interactionElement.getHandleIdentifier())); |
| if (parent != null) { |
| toRefresh.add(parent); |
| } |
| } |
| refreshViewers(toRefresh, false, false); |
| } |
| break; |
| } |
| } |
| |
| protected void refreshViewers() { |
| List<IInteractionElement> toRefresh = Collections.emptyList(); |
| refreshViewers(toRefresh, true, true); |
| } |
| |
| @Deprecated |
| protected void refreshViewers(IInteractionElement node, boolean updateLabels) { |
| refreshViewers(node, updateLabels, true); |
| } |
| |
| protected void refreshViewers(IInteractionElement node, boolean updateLabels, boolean updateExpansion) { |
| List<IInteractionElement> toRefresh = new ArrayList<IInteractionElement>(); |
| toRefresh.add(node); |
| refreshViewers(toRefresh, updateLabels, updateExpansion); |
| } |
| |
| @Deprecated |
| protected void refreshViewers(final List<IInteractionElement> nodesToRefresh, final boolean updateLabels) { |
| refreshViewers(nodesToRefresh, updateLabels, true); |
| } |
| |
| protected void refreshViewers(final List<IInteractionElement> nodesToRefresh, final boolean updateLabels, |
| final boolean updateExpansion) { |
| // TODO replace by Assert.isNotNull(nodesToRefresh); |
| if (nodesToRefresh == null) { |
| return; |
| } |
| |
| if (syncRefreshMode) { |
| internalRefresh(new HashSet<IInteractionElement>(nodesToRefresh), updateLabels, updateExpansion); |
| } else { |
| PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { |
| public void run() { |
| internalRefresh(new HashSet<IInteractionElement>(nodesToRefresh), updateLabels, updateExpansion); |
| } |
| }); |
| } |
| } |
| |
| private void internalRefresh(final Set<IInteractionElement> nodesToRefresh, final boolean updateLabels, |
| final boolean updateExpansion) { |
| try { |
| for (StructuredViewer viewer : managedViewers) { |
| refreshViewer(nodesToRefresh, updateLabels, viewer, updateExpansion); |
| } |
| } catch (Throwable t) { |
| StatusHandler.log(new Status(IStatus.ERROR, ContextUiPlugin.ID_PLUGIN, "Could not refresh viewer", t)); //$NON-NLS-1$ |
| } |
| } |
| |
| @Deprecated |
| public void refreshViewer(final Set<IInteractionElement> nodesToRefresh, final boolean updateLabels, |
| StructuredViewer viewer) { |
| refreshViewer(nodesToRefresh, updateLabels, viewer, true); |
| } |
| |
| public void refreshViewer(final Set<IInteractionElement> nodesToRefresh, final boolean updateLabels, |
| StructuredViewer viewer, boolean updateExpansion) { |
| |
| Map<StructuredViewer, FocusedViewerDelayedRefreshJob> refreshJobs = null; |
| if (updateLabels) { |
| refreshJobs = minorRefreshJobs; |
| } else { |
| refreshJobs = fullRefreshJobs; |
| } |
| FocusedViewerDelayedRefreshJob job = refreshJobs.get(viewer); |
| if (job == null) { |
| job = new FocusedViewerDelayedRefreshJob(viewer, "refresh viewer", updateLabels); //$NON-NLS-1$ |
| refreshJobs.put(viewer, job); |
| } |
| job.refreshElements(nodesToRefresh.toArray(), updateExpansion); |
| |
| } |
| |
| private void updateExpansionState(StructuredViewer viewer, Object objectToRefresh) { |
| if (viewer instanceof TreeViewer |
| && filteredViewers.contains(viewer) |
| && hasInterestFilter(viewer, true) |
| && 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 |
| if ("org.eclipse.ui.internal.views.markers.MarkersTreeViewer".equals(treeViewer.getClass() //$NON-NLS-1$ |
| .getCanonicalName())) { |
| objectToRefresh = null; |
| } |
| |
| if (objectToRefresh == null) { |
| treeViewer.expandAll(); |
| } else { |
| treeViewer.expandToLevel(objectToRefresh, AbstractTreeViewer.ALL_LEVELS); |
| } |
| } |
| } |
| |
| private boolean hasInterestFilter(StructuredViewer viewer, boolean tryToReinstall) { |
| for (ViewerFilter filter : viewer.getFilters()) { |
| if (filter instanceof InterestFilter) { |
| return true; |
| } |
| } |
| if (tryToReinstall) { |
| AbstractFocusViewAction action = focusActions.get(viewer); |
| if (action != null) { |
| action.run(action); |
| viewer.refresh(); |
| return hasInterestFilter(viewer, false); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Set to true for testing |
| */ |
| public void setSyncRefreshMode(boolean syncRefreshMode) { |
| this.syncRefreshMode = syncRefreshMode; |
| } |
| |
| public void forceRefresh() { |
| refreshViewers(); |
| } |
| |
| public static void initializeViewerSelection(IWorkbenchPart part) { |
| ISelectionProvider selectionProvider = part.getSite().getSelectionProvider(); |
| if (selectionProvider != null) { |
| ISelection selection = selectionProvider.getSelection(); |
| try { |
| if (selection != null) { |
| selectionProvider.setSelection(selection); |
| } else { |
| selectionProvider.setSelection(StructuredSelection.EMPTY); |
| } |
| } catch (UnsupportedOperationException e) { |
| // ignore if the selection does not support setting a selection, see bug 217634 |
| } |
| } |
| } |
| |
| } |