CompareEditorInput.java :  » IDE-Eclipse » All-Other-packages » org » eclipse » compare » Java Open Source

Java Open Source » IDE Eclipse » All Other packages 
All Other packages » org » eclipse » compare » CompareEditorInput.java
/*******************************************************************************
 * Copyright (c) 2000, 2009 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.compare; 

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.ResourceBundle;

import org.eclipse.compare.contentmergeviewer.IFlushable;
import org.eclipse.compare.internal.BinaryCompareViewer;
import org.eclipse.compare.internal.ChangePropertyAction;
import org.eclipse.compare.internal.CompareContentViewerSwitchingPane;
import org.eclipse.compare.internal.CompareEditor;
import org.eclipse.compare.internal.CompareEditorInputNavigator;
import org.eclipse.compare.internal.CompareMessages;
import org.eclipse.compare.internal.ComparePreferencePage;
import org.eclipse.compare.internal.CompareStructureViewerSwitchingPane;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.ICompareUIConstants;
import org.eclipse.compare.internal.OutlineViewerCreator;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.internal.ViewerDescriptor;
import org.eclipse.compare.structuremergeviewer.DiffTreeViewer;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.ISaveablesSource;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.services.IServiceLocator;


/**
 * A compare operation which can present its results in a special editor.
 * Running the compare operation and presenting the results in a compare editor
 * are combined in one class because it allows a client to keep the implementation
 * all in one place while separating it from the innards of a specific UI implementation of compare/merge.
 * <p>
 * A <code>CompareEditorInput</code> defines methods for the following sequence steps:
 * <UL>
 * <LI>running a lengthy compare operation under progress monitor control,
 * <LI>creating a UI for displaying the model and initializing the some widgets with the compare result,
 * <LI>tracking the dirty state of the model in case of merge,
 * <LI>saving the model.
 * </UL>
 * The Compare plug-in's <code>openCompareEditor</code> method takes an <code>CompareEditorInput</code>
 * and starts sequencing through the above steps. If the compare result is not empty a new compare editor
 * is opened and takes over the sequence until eventually closed.
 * <p>
 * The <code>prepareInput</code> method should contain the
 * code of the compare operation. It is executed under control of a progress monitor
 * and can be canceled. If the result of the compare is not empty, that is if there are differences
 * that needs to be presented, the <code>ICompareInput</code> should hold onto them and return them with
 * the <code>getCompareResult</code> method.
 * If the value returned from <code>getCompareResult</code> is not <code>null</code>
 * a compare editor is opened on the <code>ICompareInput</code> with title and title image initialized by the
 * corresponding methods of the <code>ICompareInput</code>.
 * <p>
 * Creation of the editor's SWT controls is delegated to the <code>createContents</code> method.
 * Here the SWT controls must be created and initialized  with the result of the compare operation.
 * <p>
 * If merging is allowed, the modification state of the compared constituents must be tracked and the dirty
 * state returned from method <code>isSaveNeeded</code>. The value <code>true</code> triggers a subsequent call
 * to <code>save</code> where the modified resources can be saved.
 * <p>
 * The most important part of this implementation is the setup of the compare/merge UI.
 * The UI uses a simple browser metaphor to present compare results.
 * The top half of the layout shows the structural compare results (e.g. added, deleted, and changed files),
 * the bottom half the content compare results (e.g. textual differences between two files).
 * A selection in the top pane is fed to the bottom pane. If a content viewer is registered
 * for the type of the selected object, this viewer is installed in the pane.
 * In addition if a structure viewer is registered for the selection type the top pane
 * is split vertically to make room for another pane and the structure viewer is installed
 * in it. When comparing Java files this second structure viewer would show the structural
 * differences within a Java file, e.g. added, deleted or changed methods and fields.
 * <p>
 * Subclasses provide custom setups, e.g. for a Catch-up/Release operation
 * by passing a subclass of <code>CompareConfiguration</code> and by implementing the <code>prepareInput</code> method.
 * If a subclass cannot use the <code>DiffTreeViewer</code> which is installed by default in the
 * top left pane, method <code>createDiffViewer</code> can be overridden.
 * <p>
 * If subclasses of this class implement {@link ISaveablesSource}, the compare editor will
 * pass these models through to the workbench. The editor will still show the dirty indicator
 * if one of these underlying models is dirty. It is the responsibility of subclasses that
 * implement this interface to call {@link #setDirty(boolean)} when the dirty state of
 * any of the models managed by the subclass change dirty state.
 * 
 * @see CompareUI
 * @see CompareEditorInput
 */
public abstract class CompareEditorInput implements IEditorInput, IPropertyChangeNotifier, IRunnableWithProgress, ICompareContainer {

  private static final boolean DEBUG= false;
  
  /**
   * The name of the "dirty" property (value <code>"DIRTY_STATE"</code>).
   */
  public static final String DIRTY_STATE= "DIRTY_STATE"; //$NON-NLS-1$
  
  /**
   * The name of the "title" property. This property is fired when the title
   * of the compare input changes. Clients should also re-obtain the tool tip
   * when this property changes.
   * @see #getTitle()
   * @since 3.3
   */
  public static final String PROP_TITLE= ICompareUIConstants.PROP_TITLE;
  
  /**
   * The name of the "title image" property. This property is fired when the title
   * image of the compare input changes.
   * @see #getTitleImage()
   * @since 3.3
   */
  public static final String PROP_TITLE_IMAGE= ICompareUIConstants.PROP_TITLE_IMAGE;
  
  /**
   * The name of the "selected edition" property. This property is fired when the selected
   * edition of the compare input changes.
   * @see #isEditionSelectionDialog()
   * @see #getSelectedEdition()
   * @since 3.3
   */
  public static final String PROP_SELECTED_EDITION= ICompareUIConstants.PROP_SELECTED_EDITION;
    
  private static final String COMPARE_EDITOR_IMAGE_NAME= "eview16/compare_view.gif"; //$NON-NLS-1$
  private static Image fgTitleImage;
  
  private Splitter fComposite;
  private CompareConfiguration fCompareConfiguration;
  private CompareViewerPane fStructureInputPane;
  private CompareViewerSwitchingPane fStructurePane1;
  private CompareViewerSwitchingPane fStructurePane2;
  private CompareViewerSwitchingPane fContentInputPane;
  private CompareViewerPane fFocusPane;
  private String fMessage;
  private Object fInput;
  private String fTitle;
  private ListenerList fListenerList= new ListenerList();
  private CompareNavigator fNavigator;
  private boolean fDirty= false;
  private ArrayList fDirtyViewers= new ArrayList();
  private IPropertyChangeListener fDirtyStateListener;
  
  boolean fStructureCompareOnSingleClick= true;

  private ICompareContainer fContainer;
  private boolean fContainerProvided;

  private String fHelpContextId;
  private InternalOutlineViewerCreator fOutlineView;
  private ViewerDescriptor fContentViewerDescriptor;
  private ViewerDescriptor fStructureViewerDescriptor;
  
  private class InternalOutlineViewerCreator extends OutlineViewerCreator {
    private OutlineViewerCreator getWrappedCreator() {
      if (fContentInputPane != null) {
        Viewer v = fContentInputPane.getViewer();
        if (v != null) {
          return (OutlineViewerCreator)Utilities.getAdapter(v, OutlineViewerCreator.class);
        }
      }
      return null;
    }
    public Viewer findStructureViewer(Viewer oldViewer,
        ICompareInput input, Composite parent,
        CompareConfiguration configuration) {
      OutlineViewerCreator creator = getWrappedCreator();
      if (creator != null)
        return creator.findStructureViewer(oldViewer, input, parent, configuration);
      return null;
    }

    public boolean hasViewerFor(Object input) {
      OutlineViewerCreator creator = getWrappedCreator();
      return (creator != null);
    }

    public Object getInput() {
      OutlineViewerCreator creator = getWrappedCreator();
      if (creator != null)
        return creator.getInput();
      return null;
    }
  }

  /**
   * Creates a <code>CompareEditorInput</code> which is initialized with the given
   * compare configuration.
   * The compare configuration is passed to subsequently created viewers.
   *
   * @param configuration the compare configuration
   */
  public CompareEditorInput(CompareConfiguration configuration) {
    fCompareConfiguration= configuration;
    Assert.isNotNull(configuration);

    fDirtyStateListener= new IPropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent e) {
        String propertyName= e.getProperty();
        if (CompareEditorInput.DIRTY_STATE.equals(propertyName)) {
          boolean changed= false;
          Object newValue= e.getNewValue();
          if (newValue instanceof Boolean)
            changed= ((Boolean)newValue).booleanValue();
          setDirty(e.getSource(), changed);
        }
      }
    };

    IPreferenceStore ps= configuration.getPreferenceStore();
    if (ps != null)
      fStructureCompareOnSingleClick= ps.getBoolean(ComparePreferencePage.OPEN_STRUCTURE_COMPARE);
    
    fContainer = configuration.getContainer();
    configuration.setContainer(this);
  }

  private boolean structureCompareOnSingleClick() {
    return fStructureCompareOnSingleClick;
  }
  
  private boolean isShowStructureInOutlineView() {
    Object object= getCompareConfiguration().getProperty(CompareConfiguration.USE_OUTLINE_VIEW);
    return (object instanceof Boolean && ((Boolean)object).booleanValue());
  }
    
  /* (non Javadoc)
   * see IAdaptable.getAdapter
   */
  public Object getAdapter(Class adapter) {
    if (ICompareNavigator.class.equals(adapter) || CompareNavigator.class.equals(adapter)) {
      return getNavigator();
    }
    if (adapter == IShowInSource.class) {
      final IFile file = (IFile)Utilities.getAdapter(this, IFile.class);
      if (file != null)
        return new IShowInSource() {
          public ShowInContext getShowInContext() {
            return new ShowInContext(new FileEditorInput(file), StructuredSelection.EMPTY);
          }
        };
    }
    if (adapter == OutlineViewerCreator.class) {
      synchronized (this) {
        if (fOutlineView == null)
          fOutlineView = new InternalOutlineViewerCreator();
        return fOutlineView;
      }
    }
    if (adapter == IFindReplaceTarget.class) {
      if (fContentInputPane != null) {
        Viewer v = fContentInputPane.getViewer();
        if (v != null) {
          return Utilities.getAdapter(v, IFindReplaceTarget.class);
        }
      }
    }
    if (adapter == IEditorInput.class) {
      if (fContentInputPane != null) {
        Viewer v = fContentInputPane.getViewer();
        if (v != null) {
          return Utilities.getAdapter(v, IEditorInput.class);
        }
      }
    }
      
    return null;
  }

  public synchronized ICompareNavigator getNavigator() {
    if (fNavigator == null)
      fNavigator= new CompareEditorInputNavigator(
        new Object[] {
          fStructureInputPane,
          fStructurePane1,
          fStructurePane2,
          fContentInputPane
        }
      );
    return fNavigator;
  }
  
  /* (non Javadoc)
   * see IEditorInput.getImageDescriptor
   */
  public ImageDescriptor getImageDescriptor() {
    return null;
  }
  
  /* (non Javadoc)
   * see IEditorInput.getToolTipText
   */
  public String getToolTipText() {
    return getTitle();
  }
  
  /* (non Javadoc)
   * see IEditorInput.getName
   */
  public String getName() {
    return getTitle();
  }
      
  /**
   * Returns <code>null</code> since this editor cannot be persisted.
   *
   * @return <code>null</code> because this editor cannot be persisted
   */
  public IPersistableElement getPersistable() {
    return null;
  }
    
  /**
   * Returns <code>false</code> to indicate that this input
   * should not appear in the "File Most Recently Used" menu.
   *
   * @return <code>false</code>
   */
  public boolean exists() {
    return false;
  }
  
  /*
   * FIXME!
    */
  protected void setMessage(String message) {
    fMessage= message;
  }
  
  /*
   * FIXME!
    */
  public String getMessage() {
    return fMessage;
  }
        
  /**
   * Returns the title which will be used in the compare editor's title bar.
   * It can be set with <code>setTitle</code>.
   *
   * @return the title
   */
  public String getTitle() {
    if (fTitle == null)
      return Utilities.getString("CompareEditorInput.defaultTitle"); //$NON-NLS-1$
    return fTitle;
  }
  
  /**
   * Sets the title which will be used when presenting the compare result.
   * This method must be called before the editor is opened.
   * 
   * @param title the title to use for the CompareEditor
   */
  public void setTitle(String title) {
    String oldTitle = fTitle;
    fTitle= title;
    Utilities.firePropertyChange(fListenerList, this, PROP_TITLE, oldTitle, title);
  }
  
  /**
   * Returns the title image which will be used in the compare editor's title bar.
   * Returns the title image which will be used when presenting the compare result.
   * This implementation returns a generic compare icon.
   * Subclasses can override.
   *
   * @return the title image, or <code>null</code> if none
   */
  public Image getTitleImage() {
    if (fgTitleImage == null) {
      fgTitleImage= CompareUIPlugin.getImageDescriptor(COMPARE_EDITOR_IMAGE_NAME).createImage();
      CompareUI.disposeOnShutdown(fgTitleImage);
    }
    return fgTitleImage;
  }
  
  /**
   * Returns the configuration object for the viewers within the compare editor.
   * Returns the configuration which was passed to the constructor.
   *
   * @return the compare configuration
   */
  public CompareConfiguration getCompareConfiguration() {
    return fCompareConfiguration;
  }

  /**
   * Adds standard actions to the given <code>ToolBarManager</code>.
   * <p>
   * Subclasses may override to add their own actions.
   * </p>
   *
   * @param toolBarManager the <code>ToolBarManager</code> to which to contribute
   */
  public void contributeToToolBar(ToolBarManager toolBarManager) {
    ResourceBundle bundle= CompareUI.getResourceBundle();
    ChangePropertyAction ignoreWhitespace= ChangePropertyAction.createIgnoreWhiteSpaceAction(bundle, getCompareConfiguration());
    toolBarManager.getControl().addDisposeListener(ignoreWhitespace);
    ChangePropertyAction showPseudoConflicts= ChangePropertyAction.createShowPseudoConflictsAction(bundle, getCompareConfiguration());
    toolBarManager.getControl().addDisposeListener(showPseudoConflicts);
    toolBarManager.add(new Separator());
    toolBarManager.add(ignoreWhitespace);
    toolBarManager.add(showPseudoConflicts);
  }
  
  /**
   * Runs the compare operation and stores the compare result.
   *
   * @param monitor the progress monitor to use to display progress and receive
   *   requests for cancelation
   * @exception InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception,
   *   it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically
   *  wrapped in an <code>InvocationTargetException</code> by the calling context
   * @exception InterruptedException if the operation detects a request to cancel,
   *  using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
   *  <code>InterruptedException</code>
   */
  public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
    fInput= prepareInput(monitor);
  }

  /**
   * Runs the compare operation and returns the compare result.
   * If <code>null</code> is returned no differences were found and no compare editor needs to be opened.
   * Progress should be reported to the given progress monitor.
   * A request to cancel the operation should be honored and acknowledged
   * by throwing <code>InterruptedException</code>.
   * <p>
   * Note: this method is typically called in a modal context thread which doesn't have a Display assigned.
   * Implementors of this method shouldn't therefore allocated any SWT resources in this method.
   * </p>
   *
   * @param monitor the progress monitor to use to display progress and receive
   *   requests for cancelation
   * @return the result of the compare operation, or <code>null</code> if there are no differences
   * @exception InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception,
   *   it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically
   *  wrapped in an <code>InvocationTargetException</code> by the calling context
   * @exception InterruptedException if the operation detects a request to cancel,
   *  using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
   *  <code>InterruptedException</code>
   */
  protected abstract Object prepareInput(IProgressMonitor monitor)
        throws InvocationTargetException, InterruptedException;
   
  /**
   * Returns the compare result computed by the most recent call to the
   * <code>run</code> method. Returns <code>null</code> if no
   * differences were found.
   *
   * @return the compare result prepared in method <code>prepareInput</code>
   *   or <code>null</code> if there were no differences
   */
  public Object getCompareResult() {
    return fInput;
  }
  
  /**
   * Create the SWT controls that are used to display the result of the compare operation.
   * Creates the SWT Controls and sets up the wiring between the individual panes.
   * This implementation creates all four panes but makes only the necessary ones visible.
   * Finally it feeds the compare result into the top left structure viewer
   * and the content viewer.
   * <p>
   * Subclasses may override if they need to change the layout or wiring between panes.
   *
   * @param parent the parent control under which the control must be created
   * @return the SWT control hierarchy for the compare editor
   */
  public Control createContents(Composite parent) {

    fComposite= new Splitter(parent, SWT.VERTICAL);
    fComposite.setData(this);
        
    Control outline= createOutlineContents(fComposite, SWT.HORIZONTAL);
          
    fContentInputPane= new CompareContentViewerSwitchingPane(fComposite, SWT.BORDER | SWT.FLAT, this);

    if (fFocusPane == null)
      fFocusPane= fContentInputPane;
    if (outline != null)
      fComposite.setVisible(outline, false);
    fComposite.setVisible(fContentInputPane, true);
    
    if (fStructureInputPane != null)
      fComposite.setWeights(new int[] { 30, 70 });
    
    fComposite.layout();

    feedInput();
  
    fComposite.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        if (!(getWorkbenchPart() instanceof CompareEditor))
          handleDispose();
      }
    });
    if (fHelpContextId != null)
      PlatformUI.getWorkbench().getHelpSystem().setHelp(fComposite, fHelpContextId);
    contentsCreated();
    return fComposite;
  }

  /**
   * Disposes this editor input. Clients should not call this method. The
   * Compare Editor calls this method at appropriate times i.e. when disposing.
   * 
   * @since 3.5
   * @nooverride This method is not intended to be re-implemented or extended
   *             by clients.
   * @noreference This method is not intended to be referenced by clients.
   */
  public void dispose() {
    handleDispose();
  }
  
  /**
   * Callback that occurs when the UI associated with this compare editor
   * input is disposed. This method will only be invoked if the UI has been
   * created (i.e. after the call to {@link #createContents(Composite)}.
   * Subclasses can extend this method but ensure that the overridden method
   * is invoked.
   * 
   * @since 3.3
   */
  protected void handleDispose() {
    fContainerProvided = false;
    fContainer = null;
    fComposite = null;
    fStructureInputPane = null;
    fStructurePane1 = null;
    fStructurePane2 = null;
    fContentInputPane = null;
    fFocusPane = null;
    fNavigator = null;
    fCompareConfiguration.dispose();
  }
  
  /**
   * Callback that occurs after the control for the input has
   * been created. If this method gets invoked then {@link #handleDispose()}
   * will be invoked when the control is disposed. Subclasses may extend this
   * method to register any listeners that need to be de-registered when the
   * input is disposed.
   * @since 3.3
   */
  protected void contentsCreated() {
    // Default is to do nothing
  }

  /**
   * @param parent the parent control under which the control must be created
   * @param direction the layout direction of the contents, either </code>SWT.HORIZONTAL<code> or </code>SWT.VERTICAL<code>
   * @return the SWT control hierarchy for the outline part of the compare editor
   * @since 3.0
   */
  public Control createOutlineContents(Composite parent, int direction) {
    final Splitter h= new Splitter(parent, direction);

    fStructureInputPane= createStructureInputPane(h);
    if (hasChildren(getCompareResult()))
      fFocusPane= fStructureInputPane;
    
    fStructurePane1= new CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this);
    h.setVisible(fStructurePane1, false);
    
    fStructurePane2= new CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this);
    h.setVisible(fStructurePane2, false);
    
    // setup the wiring for top left pane
    fStructureInputPane.addOpenListener(
      new IOpenListener() {
        public void open(OpenEvent oe) {
          feed1(oe.getSelection());
        }
      }
    );
    fStructureInputPane.addSelectionChangedListener(
      new ISelectionChangedListener() {
        public void selectionChanged(SelectionChangedEvent e) {
          ISelection s= e.getSelection();
          if (s == null || s.isEmpty())
            feed1(s);
          if (isEditionSelectionDialog())
            firePropertyChange(new PropertyChangeEvent(this, PROP_SELECTED_EDITION, null, getSelectedEdition()));
        }
      }
    );
    fStructureInputPane.addDoubleClickListener(
      new IDoubleClickListener() {
        public void doubleClick(DoubleClickEvent event) {
          feedDefault1(event.getSelection());
        }
      }
    );
    
    fStructurePane1.addSelectionChangedListener(
      new ISelectionChangedListener() {
        public void selectionChanged(SelectionChangedEvent e) {
          feed2(e.getSelection());
        }
      }
    );

    fStructurePane2.addSelectionChangedListener(
      new ISelectionChangedListener() {
        public void selectionChanged(SelectionChangedEvent e) {
          feed3(e.getSelection());
        }
      }
    );

    return h;
  }

  /**
   * Create the pane that will contain the structure input pane (upper left).
   * By default, a {@link CompareViewerSwitchingPane} is returned. Subclasses
   * may override to provide an alternate pane.
   * @param parent the parent composite
   * @return the structure input pane
   * @since 3.3
   */
  protected CompareViewerPane createStructureInputPane(
      final Composite parent) {
    return new CompareStructureViewerSwitchingPane(parent, SWT.BORDER | SWT.FLAT, true, this) {
      protected Viewer getViewer(Viewer oldViewer, Object input) {
        if (CompareEditorInput.this.hasChildren(input)) {
          return createDiffViewer(this);
        }
        return super.getViewer(oldViewer, input);
      }
    };
  }
  
  /* private */ boolean hasChildren(Object input) {
    if (input instanceof IDiffContainer) {
      IDiffContainer dn= (IDiffContainer) input;
      return (dn.hasChildren());
    }
    return false;
  }

  private void feedInput() {
    if (fStructureInputPane != null
        && (fInput instanceof ICompareInput
            || isCustomStructureInputPane())) {
      if (hasChildren(fInput) || isCustomStructureInputPane()) {
        // The input has multiple entries so set the input of the structure input pane
        fStructureInputPane.setInput(fInput);
      } else if (!structureCompareOnSingleClick() || isShowStructureInOutlineView()) {
        // We want to avoid showing the structure in the editor if we can so first
        // we'll set the content pane to see if we need to provide a structure
        internalSetContentPaneInput(fInput);
        // If the content viewer is unusable
        if (hasUnusableContentViewer()
            || (structureCompareOnSingleClick()
                && isShowStructureInOutlineView()
                && !hasOutlineViewer(fInput))) {
          fStructureInputPane.setInput(fInput);
        }
      } else {
        fStructureInputPane.setInput(fInput);
      }
      ISelection sel= fStructureInputPane.getSelection();
      if (sel == null || sel.isEmpty())
        feed1(sel);  // we only feed downstream viewers if the top left pane is empty
    }
  }

  private boolean hasOutlineViewer(Object input) {
    if (!isShowStructureInOutlineView())
      return false;
    OutlineViewerCreator creator = (OutlineViewerCreator)getAdapter(OutlineViewerCreator.class);
    if (creator != null)
      return creator.hasViewerFor(input);
    return false;
  }

  private boolean hasUnusableContentViewer() {
    return fContentInputPane.isEmpty() || fContentInputPane.getViewer() instanceof BinaryCompareViewer;
  }
  
  private boolean isCustomStructureInputPane() {
    return !(fStructureInputPane instanceof CompareViewerSwitchingPane);
  }

  private void feed1(final ISelection selection) {
    BusyIndicator.showWhile(fComposite.getDisplay(),
      new Runnable() {
        public void run() {
          if (selection == null || selection.isEmpty()) {
            Object input= fStructureInputPane.getInput();
            if (input != null)
              internalSetContentPaneInput(input);
            fStructurePane2.setInput(null); // clear downstream pane
            fStructurePane1.setInput(null);
          } else {
            Object input= getElement(selection);
            internalSetContentPaneInput(input);
            if (structureCompareOnSingleClick() || hasUnusableContentViewer())
              fStructurePane1.setInput(input);
            fStructurePane2.setInput(null); // clear downstream pane
            if (fStructurePane1.getInput() != input)
              fStructurePane1.setInput(null);
          }
        }
      }
    );
  }
  
  private void feedDefault1(final ISelection selection) {
    BusyIndicator.showWhile(fComposite.getDisplay(),
      new Runnable() {
        public void run() {
          if (!selection.isEmpty())
            fStructurePane1.setInput(getElement(selection));
        }
      }
    );
  }
  
  private void feed2(final ISelection selection) {
    BusyIndicator.showWhile(fComposite.getDisplay(),
      new Runnable() {
        public void run() {
          if (selection.isEmpty()) {
            Object input= fStructurePane1.getInput();
            internalSetContentPaneInput(input);
            fStructurePane2.setInput(null);
          } else {
            Object input= getElement(selection);
            internalSetContentPaneInput(input);
            fStructurePane2.setInput(input);
          }
        }
      }
    );
  }
  
  private void feed3(final ISelection selection) {
    BusyIndicator.showWhile(fComposite.getDisplay(),
      new Runnable() {
        public void run() {
          if (selection.isEmpty())
            internalSetContentPaneInput(fStructurePane2.getInput());
          else
            internalSetContentPaneInput(getElement(selection));
        }
      }
    );
    
  }
  
  private void internalSetContentPaneInput(Object input) {
    Object oldInput = fContentInputPane.getInput();
    fContentInputPane.setInput(input);
    if (fOutlineView != null)
      fOutlineView.fireInputChange(oldInput, input);
  }
  
  /**
   * Returns the first element of the given selection if the selection
   * is a <code>IStructuredSelection</code> with exactly one element. Returns
   * <code>null</code> otherwise.
   *
   * @param selection the selection
   * @return the first element of the selection, or <code>null</code>
   */
  private static Object getElement(ISelection selection) {
    if (selection instanceof IStructuredSelection) {
      IStructuredSelection ss= (IStructuredSelection) selection;
      if (ss.size() == 1)
        return ss.getFirstElement();
    }
    return null;
  }
  
  /**
   * Asks this input to take focus within its container (editor).
   * 
   * @noreference Clients should not call this method but they may override if
   *              they implement a different layout with different visual
   *              components. Clients are free to call the inherited method.
   * 
   * @deprecated Please use {@link #setFocus2()} instead.
   */
  public void setFocus() {
    setFocus2();
  }
  
  /**
   * Asks this input to take focus within its container (editor).
   * 
   * @noreference Clients should not call this method but they may override if
   *              they implement a different layout with different visual
   *              components. Clients are free to call the inherited method.
   * 
   * @return <code>true</code> if the input got focus, and <code>false</code>
   *         if it was unable to.
   * @since 3.5
   */
  public boolean setFocus2() {
    if (fFocusPane != null) {
      return fFocusPane.setFocus();
    } else if (fComposite != null)
      return fComposite.setFocus();
    return false;
  }
  
  /**
   * Factory method for creating a differences viewer for the top left pane.
   * It is called from <code>createContents</code> and returns a <code>DiffTreeViewer</code>.
   * <p>
   * Subclasses may override if they need a different viewer.
   * </p>
   *
   * @param parent the SWT parent control under which to create the viewer's SWT controls
   * @return a compare viewer for the top left pane
   */
  public Viewer createDiffViewer(Composite parent) {
    return new DiffTreeViewer(parent, fCompareConfiguration);
  }

  /**
   * Implements the dynamic viewer switching for structure viewers.
   * The method must return a compare viewer based on the old (or current) viewer
   * and a new input object. If the old viewer is suitable for showing the new input the old viewer
   * can be returned. Otherwise a new viewer must be created under the given parent composite or
   * <code>null</code> can be returned to indicate that no viewer could be found.
   * <p>
   * This implementation forwards the request to <code>CompareUI.findStructureViewer</code>.
   * <p>
   * Subclasses may override to implement a different strategy.
   * </p>
   * @param oldViewer a new viewer is only created if this old viewer cannot show the given input
   * @param input the input object for which to find a structure viewer
   * @param parent the SWT parent composite under which the new viewer is created
   * @return a compare viewer which is suitable for the given input object or <code>null</code>
   */
  public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent) {
    return fStructureViewerDescriptor != null ? fStructureViewerDescriptor.createViewer(oldViewer, parent,
        fCompareConfiguration) : CompareUI.findStructureViewer(oldViewer,
        input, parent, fCompareConfiguration);
  }

  /**
   * Implements the dynamic viewer switching for content viewers.
   * The method must return a compare viewer based on the old (or current) viewer
   * and a new input object. If the old viewer is suitable for showing the new input the old viewer
   * can be returned. Otherwise a new viewer must be created under the given parent composite or
   * <code>null</code> can be returned to indicate that no viewer could be found.
   * <p>
   * This implementation forwards the request to <code>CompareUI.findContentViewer</code>.
   * <p>
   * Subclasses may override to implement a different strategy.
   * </p>
   * @param oldViewer a new viewer is only created if this old viewer cannot show the given input
   * @param input the input object for which to find a structure viewer
   * @param parent the SWT parent composite under which the new viewer is created
   * @return a compare viewer which is suitable for the given input object or <code>null</code>
   */
  public Viewer findContentViewer(Viewer oldViewer, ICompareInput input, Composite parent) {

    Viewer newViewer = fContentViewerDescriptor != null ? fContentViewerDescriptor.createViewer(oldViewer, parent,
        fCompareConfiguration) : CompareUI.findContentViewer(oldViewer,
        input, parent, fCompareConfiguration);
      
    boolean isNewViewer= newViewer != oldViewer;
    if (DEBUG) System.out.println("CompareEditorInput.findContentViewer: " + isNewViewer); //$NON-NLS-1$
    
    if (isNewViewer && newViewer instanceof IPropertyChangeNotifier) {
      final IPropertyChangeNotifier dsp= (IPropertyChangeNotifier) newViewer;
      dsp.addPropertyChangeListener(fDirtyStateListener);
      
      Control c= newViewer.getControl();
      c.addDisposeListener(
        new DisposeListener() {
          public void widgetDisposed(DisposeEvent e) {
            dsp.removePropertyChangeListener(fDirtyStateListener);
          }
        }
      );
    }
    
    return newViewer;
  }
  
  /**
   * @param vd
   *            the content viewer descriptor
   * @noreference This method is not intended to be referenced by clients.
   * @nooverride This method is not intended to be re-implemented or extended
   *             by clients.
   */
  public void setContentViewerDescriptor(ViewerDescriptor vd) {
    this.fContentViewerDescriptor = vd;
  }

  /**
   * @return the content viewer descriptor set for the input
   * @noreference This method is not intended to be referenced by clients.
   * @nooverride This method is not intended to be re-implemented or extended
   *             by clients.
   */
  public ViewerDescriptor getContentViewerDescriptor() {
    return this.fContentViewerDescriptor;
  }

  /**
   * @param vd
   *            the structure viewer descriptor
   * @noreference This method is not intended to be referenced by clients.
   * @nooverride This method is not intended to be re-implemented or extended
   *             by clients.
   */
  public void setStructureViewerDescriptor(ViewerDescriptor vd) {
    this.fStructureViewerDescriptor = vd;
  }

  /**
   * @return the structure viewer descriptor set for the input
   * @noreference This method is not intended to be referenced by clients.
   * @nooverride This method is not intended to be re-implemented or extended
   *             by clients.
   */
  public ViewerDescriptor getStructureViewerDescriptor() {
    return this.fStructureViewerDescriptor;
  }
  
  /**
   * Returns <code>true</code> if there are unsaved changes.
   * The value returned is the value of the <code>DIRTY_STATE</code> property of this input object.
   
   * Returns <code>true</code> if this input has unsaved changes,
   * that is if <code>setDirty(true)</code> has been called.
   * Subclasses don't have to override if the functionality provided by <code>setDirty</code>
   * is sufficient.
   *
   * @return <code>true</code> if there are changes that need to be saved
   */
  public boolean isSaveNeeded() {
    return fDirty || fDirtyViewers.size() > 0;
  }
  
  /**
   * Returns <code>true</code> if there are unsaved changes.
   * The method should be called by any parts or dialogs
   * that contain the input.
   * By default, this method calls {@link #isSaveNeeded()}
   * but subclasses may extend.
   * @return <code>true</code> if there are unsaved changes
   * @since 3.3
   */
  public boolean isDirty() {
    return isSaveNeeded();
  }
    
  /**
   * Sets the dirty state of this input to the given
   * value and sends out a <code>PropertyChangeEvent</code> if the new value differs from the old value.
   *
   * @param dirty the dirty state for this compare input
   */
  public void setDirty(boolean dirty) {
    boolean oldDirty = fDirty || fDirtyViewers.size() > 0;
    fDirty= dirty;
    if (!fDirty)
      fDirtyViewers.clear();
    if (oldDirty != dirty)
      Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, new Boolean(oldDirty), new Boolean(dirty));
  }
  
  private void setDirty(Object source, boolean dirty) {
    Assert.isNotNull(source);
    boolean oldDirty= fDirty || fDirtyViewers.size() > 0;
    if (dirty)
      fDirtyViewers.add(source);
    else
      fDirtyViewers.remove(source);
    boolean newDirty= fDirty || fDirtyViewers.size() > 0;
    if (DEBUG) System.out.println("setDirty("+source+", "+dirty+"): " + newDirty); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    if (oldDirty != newDirty)
      Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, new Boolean(oldDirty), new Boolean(newDirty));
  }
  
  /* (non Javadoc)
   * see IPropertyChangeNotifier.addListener
   */
  public void addPropertyChangeListener(IPropertyChangeListener listener) {
    if (listener != null)
      fListenerList.add(listener);
  }

  /* (non Javadoc)
   * see IPropertyChangeNotifier.removeListener
   */
  public void removePropertyChangeListener(IPropertyChangeListener listener) {
    if (fListenerList != null) {
      fListenerList.remove(listener);
    }
  }

  /**
   * Save any unsaved changes.
   * Empty implementation.
   * Subclasses must override to save any changes.
   *
   * @param pm an <code>IProgressMonitor</code> that the implementation of save may use to show progress
   * @deprecated Override method saveChanges instead.
   */
  public void save(IProgressMonitor pm) {
    // empty default implementation
  }
  
  /**
   * Save any unsaved changes.
   * Subclasses must override to save any changes.
   * This implementation tries to flush changes in all viewers by
   * calling <code>ISavable.save</code> on them.
   *
   * @param monitor an <code>IProgressMonitor</code> that the implementation of save may use to show progress
   * @throws CoreException
   * @since 2.0
   */
  public void saveChanges(IProgressMonitor monitor) throws CoreException {
    
    flushViewers(monitor);

    save(monitor);
  }

  /**
   * Flush the viewer contents into the input.
   * @param monitor a progress monitor
   * @since 3.3
   */
  protected void flushViewers(IProgressMonitor monitor) {
    // flush changes in any dirty viewer
    flushViewer(fStructureInputPane, monitor);
    flushViewer(fStructurePane1, monitor);
    flushViewer(fStructurePane2, monitor);
    flushViewer(fContentInputPane, monitor);
  }
    
  private static void flushViewer(CompareViewerPane pane, IProgressMonitor pm) {
    if (pane != null) {
      IFlushable flushable = (IFlushable)Utilities.getAdapter(pane, IFlushable.class);
      if (flushable != null)
        flushable.flush(pm);
    }
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.compare.ICompareContainer#addCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
   */
  public void addCompareInputChangeListener(ICompareInput input,
      ICompareInputChangeListener listener) {
    if (fContainer == null) {
      input.addCompareInputChangeListener(listener);
    } else {
      fContainer.addCompareInputChangeListener(input, listener);
    }
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.compare.ICompareContainer#removeCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
   */
  public void removeCompareInputChangeListener(ICompareInput input,
      ICompareInputChangeListener listener) {
    if (fContainer == null) {
      input.removeCompareInputChangeListener(listener);
    } else {
      fContainer.removeCompareInputChangeListener(input, listener);
    }
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.compare.ICompareContainer#registerContextMenu(org.eclipse.jface.action.MenuManager, org.eclipse.jface.viewers.ISelectionProvider)
   */
  public void registerContextMenu(MenuManager menu, ISelectionProvider selectionProvider) {
    if (fContainer != null)
      fContainer.registerContextMenu(menu, selectionProvider);
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String)
   */
  public void setStatusMessage(String message) {
    if (!fContainerProvided) {
      // Try the action bars directly
      IActionBars actionBars= getActionBars();
      if (actionBars != null) {
        IStatusLineManager slm= actionBars.getStatusLineManager();
        if (slm != null) {
          slm.setMessage(message);
        }
      }
    } else if (fContainer != null) {
      fContainer.setStatusMessage(message);
    }
  }

  /* (non-Javadoc)
   * @see org.eclipse.compare.ICompareContainer#getActionBars()
   */
  public IActionBars getActionBars() {
    if (fContainer != null) {
      IActionBars actionBars = fContainer.getActionBars();
      if (actionBars == null && !fContainerProvided) {
        // The old way to find the action bars
        return Utilities.findActionBars(fComposite);
      }
      return actionBars;
    }
    return null;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.compare.ICompareContainer#getServiceLocator()
   */
  public IServiceLocator getServiceLocator() {
    IServiceLocator serviceLocator = fContainer.getServiceLocator();
    if (serviceLocator == null && !fContainerProvided) {
      // The old way to find the service locator
      return Utilities.findSite(fComposite);
    }
    return serviceLocator;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.compare.ICompareContainer#getWorkbenchPart()
   */
  public IWorkbenchPart getWorkbenchPart() {
    if (fContainer != null)
      return fContainer.getWorkbenchPart();
    return null;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.jface.operation.IRunnableContext#run(boolean, boolean, org.eclipse.jface.operation.IRunnableWithProgress)
   */
  public void run(boolean fork, boolean cancelable,
      IRunnableWithProgress runnable) throws InvocationTargetException,
      InterruptedException {
    if (fContainer != null)
      fContainer.run(fork, cancelable, runnable);
  }
  
  public void runAsynchronously(IRunnableWithProgress runnable) {
    if (fContainer != null)
      fContainer.runAsynchronously(runnable);
  }
  
  /**
   * Set the container of this input to the given container
   * @param container the container
   * @since 3.3
   */
  public void setContainer(ICompareContainer container) {
    Assert.isNotNull(container);
    this.fContainer = container;
    fContainerProvided = true;
  }

  /**
   * Return the container of this input or <code>null</code> if there is no container
   * set.
   * @return the container of this input or <code>null</code>
   * @since 3.3
   */
  public final ICompareContainer getContainer() {
    return fContainer;
  }
  
  /**
   * Fire the given property change event to all listeners
   * registered with this compare editor input.
   * @param event the property change event
   * @since 3.3
   */
  protected void firePropertyChange(PropertyChangeEvent event) {
    Utilities.firePropertyChange(fListenerList, event);
  }
  
  /**
   * Return whether this compare editor input can be run as a job.
   * By default, <code>false</code> is returned since traditionally inputs
   * were prepared in the foreground (i.e the UI was blocked when the
   * {@link #run(IProgressMonitor)} method (and indirectly the
   * {@link #prepareInput(IProgressMonitor)} method) was invoked. Subclasses
   * may override.
   * @return whether this compare editor input can be run in the background
   * @since 3.3
   */
  public boolean canRunAsJob() {
    return false;
  }

  /**
   * Return whether this input belongs to the given family
   * when it is run as a job.
   * @see #canRunAsJob()
   * @see Job#belongsTo(Object)
   * @param family the job family
   * @return whether this input belongs to the given family
   * @since 3.3
   */
  public boolean belongsTo(Object family) {
    return family == this;
  }
  
  /**
   * Return whether this input is intended to be used to select
   * a particular edition of an element in a dialog. The result
   * of this method is only consider if neither sides of the
   * input are editable. By default, <code>false</code> is returned.
   * @return whether this input is intended to be used to select
   * a particular edition of an element in a dialog
   * @see #getOKButtonLabel()
   * @see #okPressed()
   * @see #getSelectedEdition()
   * @since 3.3
   */
  public boolean isEditionSelectionDialog() {
    return false;
  }
  
  /**
   * Return the label to be used for the <code>OK</code>
   * button when this input is displayed in a dialog.
   * By default, different labels are used depending on
   * whether the input is editable or is for edition selection
   * (see {@link #isEditionSelectionDialog()}.
   * @return the label to be used for the <code>OK</code>
   * button when this input is displayed in a dialog
   * @since 3.3
   */
  public String getOKButtonLabel() {
    if (isEditable())
      return CompareMessages.CompareDialog_commit_button;
    if (isEditionSelectionDialog())
      return CompareMessages.CompareEditorInput_0;
    return IDialogConstants.OK_LABEL;
  }
  
  /**
   * Return the label used for the <code>CANCEL</code>
   * button when this input is shown in a compare dialog
   * using {@link CompareUI#openCompareDialog(CompareEditorInput)}.
   * @return the label used for the <code>CANCEL</code> button
   * @since 3.3
   */
  public String getCancelButtonLabel() {
    return IDialogConstants.CANCEL_LABEL;
  }

  private boolean isEditable() {
    return getCompareConfiguration().isLeftEditable()
      || getCompareConfiguration().isRightEditable();
  }
  
  /**
   * The <code>OK</code> button was pressed in a dialog. If one or both of
   * the sides of the input is editable then any changes will be saved. If the
   * input is for edition selection (see {@link #isEditionSelectionDialog()}),
   * it is up to subclasses to override this method in order to perform the
   * appropriate operation on the selected edition.
   * 
   * @return whether the dialog should be closed or not.
   * @since 3.3
   */
  public boolean okPressed() {
    if (isEditable()) {
      if (!saveChanges())
        return false;
    }
    return true;
  }
  
  /**
   * The <code>CANCEL</code> button was pressed in a dialog.
   * By default, nothing is done. Subclasses may override.
   * @since 3.3
   */
  public void cancelPressed() {
    // Do nothing
  }
  
  private boolean saveChanges() {
    try {
      PlatformUI.getWorkbench().getProgressService().run(true, true, new IRunnableWithProgress() {
        public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
          try {
            saveChanges(monitor);
          } catch (CoreException e) {
            throw new InvocationTargetException(e);
          }
        }
      
      });
      return true;
    } catch (InterruptedException x) {
      // Ignore
    } catch (OperationCanceledException x) {
      // Ignore
    } catch (InvocationTargetException x) {
      ErrorDialog.openError(fComposite.getShell(), CompareMessages.CompareDialog_error_title, null,
        new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0,
          NLS.bind(CompareMessages.CompareDialog_error_message, x.getTargetException().getMessage()), x.getTargetException()));
    }
    return false;
  }
  
  /**
   * Return the selected edition or <code>null</code> if no edition is selected.
   * The result of this method should only be considered if {@link #isEditionSelectionDialog()}
   * returns <code>true</code>.
   * @return the selected edition or <code>null</code>
   * @since 3.3
   */
  public Object getSelectedEdition() {
    if (fStructureInputPane != null) {
      ISelection selection = fStructureInputPane.getSelection();
      if (selection instanceof IStructuredSelection) {
        IStructuredSelection ss = (IStructuredSelection) selection;
        if (!ss.isEmpty())
          return ss.getFirstElement();
        
      }
    }
    return null;
  }
  
  /**
   * Set the help context id for this input.
   * @param helpContextId the help context id.
   * @since 3.3
   */
  public void setHelpContextId(String helpContextId) {
    this.fHelpContextId = helpContextId;
  }
  
}

java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.