| /******************************************************************************* |
| * Copyright (c) 2000, 2004 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.team.internal.ui.synchronize; |
| |
| import org.eclipse.compare.internal.INavigatable; |
| import org.eclipse.compare.structuremergeviewer.*; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.jface.action.*; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.jface.viewers.*; |
| import org.eclipse.swt.dnd.*; |
| import org.eclipse.swt.events.*; |
| import org.eclipse.swt.widgets.Menu; |
| import org.eclipse.team.internal.core.Assert; |
| import org.eclipse.team.internal.ui.*; |
| import org.eclipse.team.internal.ui.synchronize.actions.StatusLineContributionGroup; |
| import org.eclipse.team.ui.synchronize.*; |
| import org.eclipse.ui.*; |
| import org.eclipse.ui.actions.ActionContext; |
| import org.eclipse.ui.actions.ActionGroup; |
| import org.eclipse.ui.model.BaseWorkbenchContentProvider; |
| import org.eclipse.ui.part.ResourceTransfer; |
| |
| /** |
| * A <code>StructuredViewerAdvisor</code> controls various UI |
| * aspects of viewers that show {@link SyncInfoSet} like the context menu, toolbar, |
| * content provider, label provider, navigation, and model provider. The |
| * advisor allows decoupling viewer behavior from the viewers presentation. This |
| * allows viewers that aren't in the same class hierarchy to re-use basic |
| * behavior. |
| * <p> |
| * This advisor allows viewer contributions made in a plug-in manifest to |
| * be scoped to a particular unique id. As a result the context menu for the |
| * viewer can be configured to show object contributions for random id schemes. |
| * To enable declarative action contributions for a configuration there are two |
| * steps required: |
| * <ul> |
| * <li>Create a viewer contribution with a <code>targetID</code> that groups |
| * sets of actions that are related. A common pratice for synchronize view |
| * configurations is to use the participant id as the targetID. |
| * |
| * <pre> |
| * <viewerContribution |
| * id="org.eclipse.team.ccvs.ui.CVSCompareSubscriberContributions" |
| * targetID="org.eclipse.team.cvs.ui.compare-participant"> |
| * ... |
| * </pre> |
| * |
| * <li>Create a configuration instance with a <code>menuID</code> that |
| * matches the targetID in the viewer contribution. |
| * </ul> |
| * </p><p> |
| * Clients may subclass to add behavior for concrete structured viewers. |
| * </p> |
| * |
| * @see TreeViewerAdvisor |
| * @since 3.0 |
| */ |
| public abstract class StructuredViewerAdvisor implements IAdaptable { |
| |
| // The physical model shown to the user in the provided viewer. The information in |
| // this set is transformed by the model provider into the actual logical model displayed |
| // in the viewer. |
| private StructuredViewer viewer; |
| |
| // The page configuration |
| private ISynchronizePageConfiguration configuration; |
| |
| // Special actions that could not be contributed using an ActionGroup |
| private StatusLineContributionGroup statusLine; |
| private SynchronizeModelManager modelManager; |
| |
| private INavigatable nav; |
| |
| // Property change listener which reponds to: |
| // - working set selection by the user |
| // - decorator format change selected by the user |
| private IPropertyChangeListener propertyListener = new IPropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent event) { |
| // Change to showing of sync state in text labels preference |
| if(event.getProperty().equals(IPreferenceIds.SYNCVIEW_VIEW_SYNCINFO_IN_LABEL)) { |
| if(viewer != null && !viewer.getControl().isDisposed()) { |
| viewer.refresh(true /* update labels */); |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Create an advisor that will allow viewer contributions with the given <code>targetID</code>. This |
| * advisor will provide a presentation model based on the given sync info set. The model is disposed |
| * when the viewer is disposed. |
| * |
| * @param targetID the targetID defined in the viewer contributions in a plugin.xml file. |
| * @param site the workbench site with which to register the menuId. Can be <code>null</code> in which |
| * case a site will be found using the default workbench page. |
| * @param set the set of <code>SyncInfo</code> objects that are to be shown to the user. |
| */ |
| public StructuredViewerAdvisor(ISynchronizePageConfiguration configuration) { |
| this.configuration = configuration; |
| configuration.setProperty(SynchronizePageConfiguration.P_ADVISOR, this); |
| |
| // Allow the configuration to provide it's own model manager but if one isn't initialized, then |
| // simply use the default provided by the advisor. |
| modelManager = (SynchronizeModelManager)configuration.getProperty(SynchronizePageConfiguration.P_MODEL_MANAGER); |
| if(modelManager == null) { |
| modelManager = createModelManager(configuration); |
| configuration.setProperty(SynchronizePageConfiguration.P_MODEL_MANAGER, modelManager); |
| } |
| Assert.isNotNull(modelManager, "model manager must be set"); //$NON-NLS-1$ |
| modelManager.setViewerAdvisor(this); |
| } |
| |
| /** |
| * Create the model manager to be used by this advisor |
| * @param configuration |
| */ |
| protected abstract SynchronizeModelManager createModelManager(ISynchronizePageConfiguration configuration); |
| |
| /** |
| * Install a viewer to be configured with this advisor. An advisor can only be installed with |
| * one viewer at a time. When this method completes the viewer is considered initialized and |
| * can be shown to the user. |
| |
| * @param viewer the viewer being installed |
| */ |
| public final void initializeViewer(final StructuredViewer viewer) { |
| Assert.isTrue(this.viewer == null, "Can only be initialized once."); //$NON-NLS-1$ |
| Assert.isTrue(validateViewer(viewer)); |
| this.viewer = viewer; |
| |
| final DragSourceListener listener = new DragSourceListener() { |
| |
| public void dragStart(DragSourceEvent event) { |
| final IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); |
| final Object [] array= selection.toArray(); |
| event.doit= Utils.getResources(array).length > 0; |
| } |
| |
| public void dragSetData(DragSourceEvent event) { |
| |
| if (ResourceTransfer.getInstance().isSupportedType(event.dataType)) { |
| final IStructuredSelection selection= (IStructuredSelection)viewer.getSelection(); |
| final Object [] array= selection.toArray(); |
| event.data= Utils.getResources(array); |
| } |
| } |
| |
| public void dragFinished(DragSourceEvent event) {} |
| }; |
| |
| final int ops = DND.DROP_COPY | DND.DROP_LINK; |
| viewer.addDragSupport(ops, new Transfer[] { ResourceTransfer.getInstance() }, listener); |
| |
| initializeListeners(viewer); |
| viewer.setLabelProvider(getLabelProvider()); |
| viewer.setContentProvider(getContentProvider()); |
| hookContextMenu(viewer); |
| } |
| |
| /* (non-Javadoc) |
| * Allow adding an advisor to the PartNavigator and support coordinated |
| * navigation between several objects. |
| * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) |
| */ |
| public Object getAdapter(Class adapter) { |
| if(adapter == INavigatable.class) { |
| if(nav == null) { |
| nav = new INavigatable() { |
| public boolean gotoDifference(boolean next) { |
| return StructuredViewerAdvisor.this.navigate(next); |
| } |
| }; |
| } |
| return nav; |
| } |
| return null; |
| } |
| |
| private void initializeStatusLine() { |
| statusLine = new StatusLineContributionGroup( |
| configuration.getSite().getShell(), |
| configuration); |
| } |
| |
| /** |
| * Must be called when an advisor is no longer needed. |
| */ |
| public void dispose() { |
| if (statusLine != null) { |
| statusLine.dispose(); |
| } |
| if (getActionGroup() != null) { |
| getActionGroup().dispose(); |
| } |
| TeamUIPlugin.getPlugin().getPreferenceStore().removePropertyChangeListener(propertyListener); |
| } |
| |
| /** |
| * Subclasses must implement to allow navigation of their viewers. |
| * |
| * @param next if <code>true</code> then navigate forwards, otherwise navigate |
| * backwards. |
| * @return <code>true</code> if the end is reached, and <code>false</code> otherwise. |
| */ |
| public abstract boolean navigate(boolean next); |
| |
| /** |
| * Method invoked from <code>initializeViewer(Composite, StructuredViewer)</code> |
| * in order to initialize any listeners for the viewer. |
| * |
| * @param viewer the viewer being initialize |
| */ |
| protected void initializeListeners(final StructuredViewer viewer) { |
| viewer.getControl().addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| StructuredViewerAdvisor.this.dispose(); |
| } |
| }); |
| viewer.addOpenListener(new IOpenListener() { |
| public void open(OpenEvent event) { |
| handleOpen(); |
| } |
| }); |
| viewer.addDoubleClickListener(new IDoubleClickListener() { |
| public void doubleClick(DoubleClickEvent event) { |
| handleDoubleClick(viewer, event); |
| } |
| }); |
| viewer.addSelectionChangedListener(new ISelectionChangedListener() { |
| public void selectionChanged(SelectionChangedEvent event) { |
| // Update the action bars enablement for any contributed action groups |
| updateActionBars((IStructuredSelection)viewer.getSelection()); |
| } |
| }); |
| TeamUIPlugin.getPlugin().getPreferenceStore().addPropertyChangeListener(propertyListener); |
| } |
| |
| /** |
| * Handles a double-click event. If <code>false</code> is returned, |
| * subclasses may handle the double click. |
| */ |
| protected boolean handleDoubleClick(StructuredViewer viewer, DoubleClickEvent event) { |
| IStructuredSelection selection = (IStructuredSelection) event.getSelection(); |
| DiffNode node = (DiffNode) selection.getFirstElement(); |
| if (node != null && node instanceof SyncInfoModelElement) { |
| SyncInfoModelElement syncNode = (SyncInfoModelElement) node; |
| IResource resource = syncNode.getResource(); |
| if (syncNode != null && resource != null && resource.getType() == IResource.FILE) { |
| // The open is handled by the open strategy but say we handled |
| // it so that overriding methods will not do anything |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void handleOpen() { |
| Object o = getConfiguration().getProperty(SynchronizePageConfiguration.P_OPEN_ACTION); |
| if (o instanceof IAction) { |
| IAction action = (IAction)o; |
| action.run(); |
| } |
| } |
| |
| /** |
| * Subclasses can validate that the viewer being initialized with this advisor |
| * is of the correct type. |
| * |
| * @param viewer the viewer to validate |
| * @return <code>true</code> if the viewer is valid, <code>false</code> otherwise. |
| */ |
| protected abstract boolean validateViewer(StructuredViewer viewer); |
| |
| /** |
| * Returns the content provider for the viewer. |
| * |
| * @return the content provider for the viewer. |
| */ |
| protected IStructuredContentProvider getContentProvider() { |
| return new BaseWorkbenchContentProvider(); |
| } |
| |
| /** |
| * Get the label provider that will be assigned to the viewer initialized |
| * by this configuration. Subclass may override but should either wrap the |
| * default one provided by this method or subclass <code>TeamSubscriberParticipantLabelProvider</code>. |
| * In the later case, the logical label provider should still be assigned |
| * to the subclass of <code>TeamSubscriberParticipantLabelProvider</code>. |
| * @param logicalProvider |
| * the label provider for the selected logical view |
| * @return a label provider |
| * @see SynchronizeModelElementLabelProvider |
| */ |
| protected ILabelProvider getLabelProvider() { |
| ILabelProvider provider = new SynchronizeModelElementLabelProvider(); |
| ILabelDecorator[] decorators = (ILabelDecorator[])getConfiguration().getProperty(ISynchronizePageConfiguration.P_LABEL_DECORATORS); |
| if (decorators == null) { |
| return provider; |
| } |
| return new DecoratingColorLabelProvider(provider, decorators); |
| } |
| |
| /** |
| * Returns the viewer configured by this advisor. |
| * |
| * @return the viewer configured by this advisor. |
| */ |
| public final StructuredViewer getViewer() { |
| return viewer; |
| } |
| |
| /** |
| * Called to set the input to a viewer. The input to a viewer is always the model created |
| * by the model provider. |
| * |
| * @param viewer the viewer to set the input. |
| */ |
| public final void setInput(final ISynchronizeModelProvider modelProvider) { |
| final ISynchronizeModelElement modelRoot = modelProvider.getModelRoot(); |
| getActionGroup().modelChanged(modelRoot); |
| modelRoot.addCompareInputChangeListener(new ICompareInputChangeListener() { |
| public void compareInputChanged(ICompareInput source) { |
| getActionGroup().modelChanged(modelRoot); |
| } |
| }); |
| if (viewer != null) { |
| viewer.setSorter(modelProvider.getViewerSorter()); |
| viewer.setInput(modelRoot); |
| modelProvider.addPropertyChangeListener(new IPropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent event) { |
| if (event.getProperty() == ISynchronizeModelProvider.P_VIEWER_SORTER) { |
| if (viewer != null && !viewer.getControl().isDisposed()) { |
| viewer.getControl().getDisplay().syncExec(new Runnable() { |
| public void run() { |
| if (viewer != null && !viewer.getControl().isDisposed()) { |
| viewer.setSorter(modelProvider.getViewerSorter()); |
| } |
| } |
| }); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * @return Returns the configuration. |
| */ |
| public ISynchronizePageConfiguration getConfiguration() { |
| return configuration; |
| } |
| |
| /** |
| * Method invoked from the synchronize page when the action |
| * bars are set. The advisor uses the configuration to determine |
| * which groups appear in the action bar menus and allows all |
| * action groups registered with the configuration to fill the action bars. |
| * @param actionBars the Action bars for the page |
| */ |
| public final void setActionBars(IActionBars actionBars) { |
| if(actionBars != null) { |
| IToolBarManager manager = actionBars.getToolBarManager(); |
| |
| // Populate the toobar menu with the configured groups |
| Object o = configuration.getProperty(ISynchronizePageConfiguration.P_TOOLBAR_MENU); |
| if (!(o instanceof String[])) { |
| o = ISynchronizePageConfiguration.DEFAULT_TOOLBAR_MENU; |
| } |
| String[] groups = (String[])o; |
| for (int i = 0; i < groups.length; i++) { |
| String group = groups[i]; |
| // The groupIds must be converted to be unique since the toolbar is shared |
| manager.add(new Separator(getGroupId(group))); |
| } |
| |
| // view menu |
| IMenuManager menu = actionBars.getMenuManager(); |
| if (menu != null) { |
| // Populate the view dropdown menu with the configured groups |
| o = configuration.getProperty(ISynchronizePageConfiguration.P_VIEW_MENU); |
| if (!(o instanceof String[])) { |
| o = ISynchronizePageConfiguration.DEFAULT_VIEW_MENU; |
| } |
| groups = (String[]) o; |
| initializeStatusLine(); |
| for (int i = 0; i < groups.length; i++) { |
| String group = groups[i]; |
| // The groupIds must be converted to be unique since the |
| // view menu is shared |
| menu.add(new Separator(getGroupId(group))); |
| } |
| } |
| // status line |
| IStatusLineManager statusLineMgr = actionBars.getStatusLineManager(); |
| if (statusLineMgr != null && statusLine != null) { |
| statusLine.fillActionBars(actionBars); |
| } |
| |
| getActionGroup().fillActionBars(actionBars); |
| updateActionBars((IStructuredSelection) getViewer().getSelection()); |
| Object input = viewer.getInput(); |
| if (input instanceof ISynchronizeModelElement) { |
| getActionGroup().modelChanged((ISynchronizeModelElement) input); |
| } |
| } |
| } |
| |
| /* |
| * Method invoked from <code>initializeViewer(StructuredViewer)</code> |
| * in order to configure the viewer to call <code>fillContextMenu(StructuredViewer, IMenuManager)</code> |
| * when a context menu is being displayed in viewer. |
| * |
| * @param viewer the viewer being initialized |
| * @see fillContextMenu(StructuredViewer, IMenuManager) |
| */ |
| private void hookContextMenu(final StructuredViewer viewer) { |
| String targetID; |
| Object o = configuration.getProperty(ISynchronizePageConfiguration.P_OBJECT_CONTRIBUTION_ID); |
| if (o instanceof String) { |
| targetID = (String)o; |
| } else { |
| targetID = null; |
| } |
| final MenuManager menuMgr = new MenuManager(targetID); //$NON-NLS-1$ |
| |
| menuMgr.setRemoveAllWhenShown(true); |
| menuMgr.addMenuListener(new IMenuListener() { |
| |
| public void menuAboutToShow(IMenuManager manager) { |
| fillContextMenu(viewer, manager); |
| } |
| }); |
| Menu menu = menuMgr.createContextMenu(viewer.getControl()); |
| menu.addMenuListener(new MenuListener() { |
| |
| public void menuHidden(MenuEvent e) { |
| } |
| |
| // Hack to allow action contributions to update their |
| // state before the menu is shown. This is required when |
| // the state of the selection changes and the contributions |
| // need to update enablement based on this. |
| // TODO: Is this hack still needed |
| public void menuShown(MenuEvent e) { |
| IContributionItem[] items = menuMgr.getItems(); |
| for (int i = 0; i < items.length; i++) { |
| IContributionItem item = items[i]; |
| if (item instanceof ActionContributionItem) { |
| IAction actionItem = ((ActionContributionItem) item).getAction(); |
| if (actionItem instanceof SynchronizeModelAction) { |
| ((SynchronizeModelAction) actionItem).selectionChanged(viewer.getSelection()); |
| } |
| } |
| } |
| } |
| }); |
| viewer.getControl().setMenu(menu); |
| if (targetID != null) { |
| IWorkbenchSite workbenchSite = configuration.getSite().getWorkbenchSite(); |
| IWorkbenchPartSite ws = null; |
| if (workbenchSite instanceof IWorkbenchPartSite) |
| ws = (IWorkbenchPartSite)workbenchSite; |
| if (ws != null) { |
| ws.registerContextMenu(targetID, menuMgr, viewer); |
| } |
| } |
| } |
| |
| /* |
| * Callback that is invoked when a context menu is about to be shown in the |
| * viewer. Subsclasses must implement to contribute menus. Also, menus can |
| * contributed by creating a viewer contribution with a <code>targetID</code> |
| * that groups sets of actions that are related. |
| * |
| * @param viewer the viewer in which the context menu is being shown. |
| * @param manager the menu manager to which actions can be added. |
| */ |
| private void fillContextMenu(StructuredViewer viewer, final IMenuManager manager) { |
| // Populate the menu with the configured groups |
| Object o = configuration.getProperty(ISynchronizePageConfiguration.P_CONTEXT_MENU); |
| if (!(o instanceof String[])) { |
| o = ISynchronizePageConfiguration.DEFAULT_CONTEXT_MENU; |
| } |
| String[] groups = (String[])o; |
| for (int i = 0; i < groups.length; i++) { |
| String group = groups[i]; |
| // There is no need to adjust the group ids in a contetx menu (see setActionBars) |
| manager.add(new Separator(group)); |
| } |
| getActionGroup().setContext(new ActionContext(viewer.getSelection())); |
| getActionGroup().fillContextMenu(manager); |
| } |
| |
| private void updateActionBars(IStructuredSelection selection) { |
| ActionGroup group = getActionGroup(); |
| if (group != null) { |
| group.setContext(new ActionContext(selection)); |
| group.updateActionBars(); |
| } |
| } |
| |
| private SynchronizePageActionGroup getActionGroup() { |
| return (SynchronizePageActionGroup)configuration; |
| } |
| |
| private String getGroupId(String group) { |
| return ((SynchronizePageConfiguration)configuration).getGroupId(group); |
| } |
| |
| /* |
| * For use by test cases only |
| * @return Returns the modelManager. |
| */ |
| public SynchronizeModelManager getModelManager() { |
| return modelManager; |
| } |
| } |