| /******************************************************************************* |
| * Copyright (c) 2000, 2016 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 |
| * Alex Blewitt <alex.blewitt@gmail.com> - replace new Boolean with Boolean.valueOf - https://bugs.eclipse.org/470344 |
| * Stefan Xenos <sxenos@gmail.com> (Google) - bug 448968 - Add diagnostic logging |
| * Conrad Groth - Bug 213780 - Compare With direction should be configurable |
| *******************************************************************************/ |
| |
| package org.eclipse.compare.contentmergeviewer; |
| |
| import java.io.IOException; |
| import java.util.ResourceBundle; |
| |
| import org.eclipse.compare.CompareConfiguration; |
| import org.eclipse.compare.CompareEditorInput; |
| import org.eclipse.compare.CompareUI; |
| import org.eclipse.compare.CompareViewerPane; |
| import org.eclipse.compare.ICompareContainer; |
| import org.eclipse.compare.ICompareInputLabelProvider; |
| import org.eclipse.compare.IPropertyChangeNotifier; |
| import org.eclipse.compare.internal.ChangePropertyAction; |
| import org.eclipse.compare.internal.CompareEditor; |
| import org.eclipse.compare.internal.CompareHandlerService; |
| import org.eclipse.compare.internal.CompareMessages; |
| import org.eclipse.compare.internal.ComparePreferencePage; |
| import org.eclipse.compare.internal.CompareUIPlugin; |
| import org.eclipse.compare.internal.ICompareUIConstants; |
| import org.eclipse.compare.internal.IFlushable2; |
| import org.eclipse.compare.internal.ISavingSaveable; |
| import org.eclipse.compare.internal.MergeViewerContentProvider; |
| import org.eclipse.compare.internal.MirroredMergeViewerContentProvider; |
| import org.eclipse.compare.internal.Policy; |
| import org.eclipse.compare.internal.Utilities; |
| import org.eclipse.compare.internal.ViewerSwitchingCancelled; |
| import org.eclipse.compare.structuremergeviewer.Differencer; |
| import org.eclipse.compare.structuremergeviewer.ICompareInput; |
| import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.ActionContributionItem; |
| import org.eclipse.jface.action.IToolBarManager; |
| import org.eclipse.jface.action.LegacyActionTools; |
| import org.eclipse.jface.action.Separator; |
| import org.eclipse.jface.action.ToolBarManager; |
| import org.eclipse.jface.dialogs.IDialogConstants; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.preference.IPersistentPreferenceStore; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.jface.viewers.ContentViewer; |
| import org.eclipse.jface.viewers.IContentProvider; |
| import org.eclipse.jface.viewers.ILabelProviderListener; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.LabelProviderChangedEvent; |
| import org.eclipse.jface.window.Window; |
| import org.eclipse.osgi.util.TextProcessor; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.CLabel; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseMoveListener; |
| import org.eclipse.swt.graphics.Cursor; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Layout; |
| import org.eclipse.swt.widgets.Sash; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.ISaveablesSource; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.Saveable; |
| |
| |
| /** |
| * An abstract compare and merge viewer with two side-by-side content areas |
| * and an optional content area for the ancestor. The implementation makes no |
| * assumptions about the content type. |
| * <p> |
| * <code>ContentMergeViewer</code> |
| * <ul> |
| * <li>implements the overall layout and defines hooks so that subclasses |
| * can easily provide an implementation for a specific content type, |
| * <li>implements the UI for making the areas resizable, |
| * <li>has an action for controlling whether the ancestor area is visible or not, |
| * <li>has actions for copying one side of the input to the other side, |
| * <li>tracks the dirty state of the left and right sides and send out notification |
| * on state changes. |
| * </ul> |
| * A <code>ContentMergeViewer</code> accesses its |
| * model by means of a content provider which must implement the |
| * <code>IMergeViewerContentProvider</code> interface. |
| * </p> |
| * <p> |
| * Clients may wish to use the standard concrete subclass <code>TextMergeViewer</code>, |
| * or define their own subclass. |
| * |
| * @see IMergeViewerContentProvider |
| * @see TextMergeViewer |
| */ |
| public abstract class ContentMergeViewer extends ContentViewer |
| implements IPropertyChangeNotifier, IFlushable, IFlushable2 { |
| /* package */ static final int HORIZONTAL= 1; |
| /* package */ static final int VERTICAL= 2; |
| |
| static final double HSPLIT= 0.5; |
| static final double VSPLIT= 0.3; |
| |
| private class ContentMergeViewerLayout extends Layout { |
| @Override |
| public Point computeSize(Composite c, int w, int h, boolean force) { |
| return new Point(100, 100); |
| } |
| |
| @Override |
| public void layout(Composite composite, boolean force) { |
| if (fLeftLabel == null) { |
| if (composite.isDisposed()) { |
| CompareUIPlugin |
| .log(new IllegalArgumentException("Attempted to perform a layout on a disposed composite")); //$NON-NLS-1$ |
| } |
| if (Policy.debugContentMergeViewer) { |
| logTrace("found bad label. Layout = " + System.identityHashCode(this) + ". composite = " //$NON-NLS-1$//$NON-NLS-2$ |
| + System.identityHashCode(composite) + ". fComposite = " //$NON-NLS-1$ |
| + System.identityHashCode(fComposite) + ". fComposite.isDisposed() = " //$NON-NLS-1$ |
| + fComposite.isDisposed()); |
| logStackTrace(); |
| } |
| // Help to find out the cause for bug 449558 |
| NullPointerException npe= new NullPointerException("fLeftLabel is 'null';fLeftLabelSet is " + fLeftLabelSet + ";fComposite.isDisposed() is " + fComposite.isDisposed()); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| // Allow to test whether doing nothing helps |
| if (Boolean.getBoolean("ContentMergeViewer.DEBUG")) { //$NON-NLS-1$ |
| CompareUIPlugin.log(npe); |
| return; |
| } |
| |
| throw npe; |
| } |
| |
| // determine some derived sizes |
| int headerHeight= fLeftLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y; |
| Rectangle r= composite.getClientArea(); |
| |
| int centerWidth= getCenterWidth(); |
| int width1= (int) ((r.width - centerWidth) * getHorizontalSplitRatio()); |
| int width2= r.width - width1 - centerWidth; |
| |
| int height1= 0; |
| int height2= 0; |
| if (fIsThreeWay && fAncestorVisible) { |
| height1= (int) ((r.height - (2 * headerHeight)) * fVSplit); |
| height2= r.height - (2 * headerHeight) - height1; |
| } else { |
| height1= 0; |
| height2= r.height - headerHeight; |
| } |
| |
| int y= 0; |
| |
| if (fIsThreeWay && fAncestorVisible) { |
| fAncestorLabel.setBounds(0, y, r.width, headerHeight); |
| fAncestorLabel.setVisible(true); |
| y+= headerHeight; |
| handleResizeAncestor(0, y, r.width, height1); |
| y+= height1; |
| } else { |
| fAncestorLabel.setVisible(false); |
| handleResizeAncestor(0, 0, 0, 0); |
| } |
| |
| fLeftLabel.getSize(); // without this resizing would not always work |
| |
| if (centerWidth > 3) { |
| fLeftLabel.setBounds(0, y, width1 + 1, headerHeight); |
| fDirectionLabel.setVisible(true); |
| fDirectionLabel.setBounds(width1 + 1, y, centerWidth - 1, headerHeight); |
| fRightLabel.setBounds(width1+centerWidth, y, width2, headerHeight); |
| } else { |
| fLeftLabel.setBounds(0, y, width1, headerHeight); |
| fDirectionLabel.setVisible(false); |
| fRightLabel.setBounds(width1, y, r.width - width1, headerHeight); |
| } |
| |
| y+= headerHeight; |
| |
| if (fCenter != null && !fCenter.isDisposed()) |
| fCenter.setBounds(width1, y, centerWidth, height2); |
| |
| handleResizeLeftRight(0, y, width1, centerWidth, width2, height2); |
| } |
| |
| private double getHorizontalSplitRatio() { |
| if (fHSplit < 0) { |
| Object input = getInput(); |
| if (input instanceof ICompareInput) { |
| ICompareInput ci = (ICompareInput) input; |
| if (ci.getLeft() == null) |
| return 0.1; |
| if (ci.getRight() == null) |
| return 0.9; |
| } |
| return HSPLIT; |
| } |
| return fHSplit; |
| } |
| } |
| |
| class Resizer extends MouseAdapter implements MouseMoveListener { |
| Control fControl; |
| int fX, fY; |
| int fWidth1, fWidth2; |
| int fHeight1, fHeight2; |
| int fDirection; |
| boolean fLiveResize; |
| boolean fIsDown; |
| |
| public Resizer(Control c, int dir) { |
| fDirection= dir; |
| fControl= c; |
| fLiveResize= !(fControl instanceof Sash); |
| updateCursor(c, dir); |
| fControl.addMouseListener(this); |
| fControl.addMouseMoveListener(this); |
| fControl.addDisposeListener( |
| new DisposeListener() { |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| fControl= null; |
| } |
| } |
| ); |
| } |
| |
| @Override |
| public void mouseDoubleClick(MouseEvent e) { |
| if ((fDirection & HORIZONTAL) != 0) |
| fHSplit= -1; |
| if ((fDirection & VERTICAL) != 0) |
| fVSplit= VSPLIT; |
| fComposite.layout(true); |
| } |
| |
| @Override |
| public void mouseDown(MouseEvent e) { |
| Composite parent= fControl.getParent(); |
| |
| Point s= parent.getSize(); |
| Point as= fAncestorLabel.getSize(); |
| Point ys= fLeftLabel.getSize(); |
| Point ms= fRightLabel.getSize(); |
| |
| fWidth1= ys.x; |
| fWidth2= ms.x; |
| fHeight1= fLeftLabel.getLocation().y - as.y; |
| fHeight2= s.y - (fLeftLabel.getLocation().y + ys.y); |
| |
| fX= e.x; |
| fY= e.y; |
| fIsDown= true; |
| } |
| |
| @Override |
| public void mouseUp(MouseEvent e) { |
| fIsDown= false; |
| if (!fLiveResize) |
| resize(e); |
| } |
| |
| @Override |
| public void mouseMove(MouseEvent e) { |
| if (fIsDown && fLiveResize) |
| resize(e); |
| } |
| |
| private void resize(MouseEvent e) { |
| int dx= e.x-fX; |
| int dy= e.y-fY; |
| |
| int centerWidth= fCenter.getSize().x; |
| |
| if (fWidth1 + dx > centerWidth && fWidth2 - dx > centerWidth) { |
| fWidth1 += dx; |
| fWidth2 -= dx; |
| if ((fDirection & HORIZONTAL) != 0) |
| fHSplit= (double) fWidth1 / (double) (fWidth1 + fWidth2); |
| } |
| if (fHeight1 + dy > centerWidth && fHeight2 - dy > centerWidth) { |
| fHeight1 += dy; |
| fHeight2 -= dy; |
| if ((fDirection & VERTICAL) != 0) |
| fVSplit= (double) fHeight1 / (double) (fHeight1 + fHeight2); |
| } |
| |
| fComposite.layout(true); |
| fControl.getDisplay().update(); |
| } |
| } |
| |
| /** Style bits for top level composite */ |
| private int fStyles; |
| private ResourceBundle fBundle; |
| private final CompareConfiguration fCompareConfiguration; |
| private IPropertyChangeListener fPropertyChangeListener; |
| private ICompareInputChangeListener fCompareInputChangeListener; |
| private ListenerList<IPropertyChangeListener> fListenerList; |
| boolean fConfirmSave= true; |
| |
| private double fHSplit= -1; // width ratio of left and right panes |
| private double fVSplit= VSPLIT; // height ratio of ancestor and bottom panes |
| |
| private boolean fIsThreeWay; // whether their is an ancestor |
| private boolean fAncestorVisible; // whether the ancestor pane is visible |
| private ActionContributionItem fAncestorItem; |
| |
| private ActionContributionItem copyLeftToRightItem; // copy from left to right |
| private ActionContributionItem copyRightToLeftItem; // copy from right to left |
| |
| private boolean fIsLeftDirty; |
| private boolean fIsRightDirty; |
| |
| private CompareHandlerService fHandlerService; |
| |
| private final MergeViewerContentProvider fDefaultContentProvider; |
| private Action fSwitchLeftAndRight; |
| |
| // SWT widgets |
| /* package */ Composite fComposite; |
| private CLabel fAncestorLabel; |
| private CLabel fLeftLabel; |
| |
| private boolean fLeftLabelSet= false; // needed for debug output for bug 449558 |
| private CLabel fRightLabel; |
| /* package */ CLabel fDirectionLabel; |
| /* package */ Control fCenter; |
| |
| //---- SWT resources to be disposed |
| private Image fRightArrow; |
| private Image fLeftArrow; |
| private Image fBothArrow; |
| Cursor fNormalCursor; |
| private Cursor fHSashCursor; |
| private Cursor fVSashCursor; |
| private Cursor fHVSashCursor; |
| |
| private ILabelProviderListener labelChangeListener = new ILabelProviderListener() { |
| @Override |
| public void labelProviderChanged(LabelProviderChangedEvent event) { |
| Object[] elements = event.getElements(); |
| for (int i = 0; i < elements.length; i++) { |
| Object object = elements[i]; |
| if (object == getInput()) |
| updateHeader(); |
| } |
| } |
| }; |
| |
| //---- end |
| |
| /** |
| * Creates a new content merge viewer and initializes with a resource bundle and a |
| * configuration. |
| * |
| * @param style SWT style bits |
| * @param bundle the resource bundle |
| * @param cc the configuration object |
| */ |
| protected ContentMergeViewer(int style, ResourceBundle bundle, CompareConfiguration cc) { |
| if (Policy.debugContentMergeViewer) { |
| logTrace("constructed (fLeftLabel == null)"); //$NON-NLS-1$ |
| logStackTrace(); |
| } |
| |
| fStyles= style & ~(SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT); // remove BIDI direction bits |
| fBundle= bundle; |
| |
| fAncestorVisible= Utilities.getBoolean(cc, ICompareUIConstants.PROP_ANCESTOR_VISIBLE, fAncestorVisible); |
| fConfirmSave= Utilities.getBoolean(cc, CompareEditor.CONFIRM_SAVE_PROPERTY, fConfirmSave); |
| |
| fCompareInputChangeListener = (input) -> { if (input == getInput()) handleCompareInputChange(); }; |
| |
| // Make sure the compare configuration is not null |
| fCompareConfiguration = cc != null ? cc : new CompareConfiguration(); |
| fPropertyChangeListener = (event) -> handlePropertyChangeEvent(event); |
| fCompareConfiguration.addPropertyChangeListener(fPropertyChangeListener); |
| |
| fDefaultContentProvider = new MergeViewerContentProvider(fCompareConfiguration); |
| updateContentProvider(); |
| |
| fIsLeftDirty = false; |
| fIsRightDirty = false; |
| } |
| |
| private void logStackTrace() { |
| new Exception("<Fake exception> in " + getClass().getName()).printStackTrace(System.out); //$NON-NLS-1$ |
| } |
| |
| private void logTrace(String string) { |
| System.out.println("ContentMergeViewer " + System.identityHashCode(this) + ": " + string); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| //---- hooks --------------------- |
| |
| |
| /** |
| * Returns the viewer's name. |
| * |
| * @return the viewer's name |
| */ |
| public String getTitle() { |
| return Utilities.getString(getResourceBundle(), "title"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Creates the SWT controls for the ancestor, left, and right |
| * content areas of this compare viewer. |
| * Implementations typically hold onto the controls |
| * so that they can be initialized with the input objects in method |
| * <code>updateContent</code>. |
| * |
| * @param composite the container for the three areas |
| */ |
| abstract protected void createControls(Composite composite); |
| |
| /** |
| * Lays out the ancestor area of the compare viewer. |
| * It is called whenever the viewer is resized or when the sashes between |
| * the areas are moved to adjust the size of the areas. |
| * |
| * @param x the horizontal position of the ancestor area within its container |
| * @param y the vertical position of the ancestor area within its container |
| * @param width the width of the ancestor area |
| * @param height the height of the ancestor area |
| */ |
| abstract protected void handleResizeAncestor(int x, int y, int width, int height); |
| |
| /** |
| * Lays out the left and right areas of the compare viewer. |
| * It is called whenever the viewer is resized or when the sashes between |
| * the areas are moved to adjust the size of the areas. |
| * |
| * @param x the horizontal position of the left area within its container |
| * @param y the vertical position of the left and right area within its container |
| * @param leftWidth the width of the left area |
| * @param centerWidth the width of the gap between the left and right areas |
| * @param rightWidth the width of the right area |
| * @param height the height of the left and right areas |
| */ |
| abstract protected void handleResizeLeftRight(int x, int y, int leftWidth, int centerWidth, |
| int rightWidth, int height); |
| |
| /** |
| * Contributes items to the given <code>ToolBarManager</code>. |
| * It is called when this viewer is installed in its container and if the container |
| * has a <code>ToolBarManager</code>. |
| * The <code>ContentMergeViewer</code> implementation of this method does nothing. |
| * Subclasses may reimplement. |
| * |
| * @param toolBarManager the toolbar manager to contribute to |
| */ |
| protected void createToolItems(ToolBarManager toolBarManager) { |
| // empty implementation |
| } |
| |
| /** |
| * Initializes the controls of the three content areas with the given input objects. |
| * |
| * @param ancestor the input for the ancestor area |
| * @param left the input for the left area |
| * @param right the input for the right area |
| */ |
| abstract protected void updateContent(Object ancestor, Object left, Object right); |
| |
| /** |
| * Copies the content of one side to the other side. |
| * Called from the (internal) actions for copying the sides of the viewer's input object. |
| * |
| * @param leftToRight if <code>true</code>, the left side is copied to the right side; |
| * if <code>false</code>, the right side is copied to the left side |
| */ |
| abstract protected void copy(boolean leftToRight); |
| |
| /** |
| * Returns the byte contents of the left or right side. If the viewer |
| * has no editable content <code>null</code> can be returned. |
| * |
| * @param left if <code>true</code>, the byte contents of the left area is returned; |
| * if <code>false</code>, the byte contents of the right area |
| * @return the content as an array of bytes, or <code>null</code> |
| */ |
| abstract protected byte[] getContents(boolean left); |
| |
| //---------------------------- |
| |
| /** |
| * Returns the resource bundle of this viewer. |
| * |
| * @return the resource bundle |
| */ |
| protected ResourceBundle getResourceBundle() { |
| return fBundle; |
| } |
| |
| /** |
| * Returns the compare configuration of this viewer. |
| * |
| * @return the compare configuration, never <code>null</code> |
| */ |
| protected CompareConfiguration getCompareConfiguration() { |
| return fCompareConfiguration; |
| } |
| |
| /** |
| * The <code>ContentMergeViewer</code> implementation of this |
| * <code>ContentViewer</code> method |
| * checks to ensure that the content provider is an <code>IMergeViewerContentProvider</code>. |
| * @param contentProvider the content provider to set. Must implement IMergeViewerContentProvider. |
| */ |
| @Override |
| public void setContentProvider(IContentProvider contentProvider) { |
| Assert.isTrue(contentProvider instanceof IMergeViewerContentProvider); |
| super.setContentProvider(contentProvider); |
| } |
| |
| private void updateContentProvider() { |
| setContentProvider(getCompareConfiguration().isMirrored() |
| ? new MirroredMergeViewerContentProvider(getCompareConfiguration(), fDefaultContentProvider) |
| : fDefaultContentProvider); |
| } |
| |
| /* package */ IMergeViewerContentProvider getMergeContentProvider() { |
| return (IMergeViewerContentProvider) getContentProvider(); |
| } |
| |
| /** |
| * The <code>ContentMergeViewer</code> implementation of this |
| * <code>Viewer</code> method returns the empty selection. Subclasses may override. |
| * @return empty selection. |
| */ |
| @Override |
| public ISelection getSelection() { |
| return new ISelection() { |
| @Override |
| public boolean isEmpty() { |
| return true; |
| } |
| }; |
| } |
| |
| /** |
| * The <code>ContentMergeViewer</code> implementation of this |
| * <code>Viewer</code> method does nothing. Subclasses may reimplement. |
| * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean) |
| */ |
| @Override |
| public void setSelection(ISelection selection, boolean reveal) { |
| // Empty implementation. |
| } |
| |
| /** |
| * Callback that is invoked when a property in the compare configuration |
| * ({@link #getCompareConfiguration()} changes. |
| * @param event the property change event |
| * @since 3.3 |
| */ |
| protected void handlePropertyChangeEvent(PropertyChangeEvent event) { |
| String key= event.getProperty(); |
| |
| if (key.equals(ICompareUIConstants.PROP_ANCESTOR_VISIBLE)) { |
| fAncestorVisible= Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_ANCESTOR_VISIBLE, fAncestorVisible); |
| fComposite.layout(true); |
| |
| updateCursor(fLeftLabel, VERTICAL); |
| updateCursor(fDirectionLabel, HORIZONTAL | VERTICAL); |
| updateCursor(fRightLabel, VERTICAL); |
| |
| return; |
| } |
| |
| if (key.equals(ICompareUIConstants.PROP_IGNORE_ANCESTOR)) { |
| setAncestorVisibility(false, !Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_IGNORE_ANCESTOR, false)); |
| return; |
| } |
| |
| if (key.equals(ComparePreferencePage.SWAPPED)) { |
| getCompareConfiguration().setProperty(CompareConfiguration.MIRRORED, event.getNewValue()); |
| updateContentProvider(); |
| updateToolItems(); |
| return; |
| } |
| } |
| |
| void updateCursor(Control c, int dir) { |
| if (!(c instanceof Sash)) { |
| Cursor cursor= null; |
| switch (dir) { |
| case VERTICAL: |
| if (fAncestorVisible) { |
| if (fVSashCursor == null) |
| fVSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZENS); |
| cursor= fVSashCursor; |
| } else { |
| if (fNormalCursor == null) |
| fNormalCursor= new Cursor(c.getDisplay(), SWT.CURSOR_ARROW); |
| cursor= fNormalCursor; |
| } |
| break; |
| case HORIZONTAL: |
| if (fHSashCursor == null) |
| fHSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZEWE); |
| cursor= fHSashCursor; |
| break; |
| case VERTICAL + HORIZONTAL: |
| if (fAncestorVisible) { |
| if (fHVSashCursor == null) |
| fHVSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZEALL); |
| cursor= fHVSashCursor; |
| } else { |
| if (fHSashCursor == null) |
| fHSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZEWE); |
| cursor= fHSashCursor; |
| } |
| break; |
| } |
| if (cursor != null) |
| c.setCursor(cursor); |
| } |
| } |
| |
| private void setAncestorVisibility(boolean visible, boolean enabled) { |
| if (fAncestorItem != null) { |
| Action action= (Action) fAncestorItem.getAction(); |
| if (action != null) { |
| action.setChecked(visible); |
| action.setEnabled(enabled); |
| } |
| } |
| getCompareConfiguration().setProperty(ICompareUIConstants.PROP_ANCESTOR_VISIBLE, Boolean.valueOf(visible)); |
| } |
| |
| //---- input |
| |
| /** |
| * Return whether the input is a three-way comparison. |
| * @return whether the input is a three-way comparison |
| * @since 3.3 |
| */ |
| protected boolean isThreeWay() { |
| return fIsThreeWay; |
| } |
| |
| /** |
| * Internal hook method called when the input to this viewer is |
| * initially set or subsequently changed. |
| * <p> |
| * The <code>ContentMergeViewer</code> implementation of this <code>Viewer</code> |
| * method tries to save the old input by calling <code>doSave(...)</code> and |
| * then calls <code>internalRefresh(...)</code>. |
| * |
| * @param input the new input of this viewer, or <code>null</code> if there is no new input |
| * @param oldInput the old input element, or <code>null</code> if there was previously no input |
| */ |
| @Override |
| protected final void inputChanged(Object input, Object oldInput) { |
| if (input != oldInput && oldInput != null) { |
| ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider(); |
| if (lp != null) |
| lp.removeListener(labelChangeListener); |
| } |
| |
| if (input != oldInput && oldInput instanceof ICompareInput) { |
| ICompareContainer container = getCompareConfiguration().getContainer(); |
| container.removeCompareInputChangeListener((ICompareInput)oldInput, fCompareInputChangeListener); |
| } |
| |
| boolean success= doSave(input, oldInput); |
| |
| if (input != oldInput && input instanceof ICompareInput) { |
| ICompareContainer container = getCompareConfiguration().getContainer(); |
| container.addCompareInputChangeListener((ICompareInput)input, fCompareInputChangeListener); |
| } |
| |
| if (input != oldInput && input != null) { |
| ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider(); |
| if (lp != null) |
| lp.addListener(labelChangeListener); |
| } |
| |
| if (success) { |
| setLeftDirty(false); |
| setRightDirty(false); |
| } |
| |
| if (input != oldInput) |
| internalRefresh(input); |
| } |
| |
| /** |
| * This method is called from the <code>Viewer</code> method <code>inputChanged</code> |
| * to save any unsaved changes of the old input. |
| * <p> |
| * The <code>ContentMergeViewer</code> implementation of this |
| * method calls <code>saveContent(...)</code>. If confirmation has been turned on |
| * with <code>setConfirmSave(true)</code>, a confirmation alert is posted before saving. |
| * </p> |
| * Clients can override this method and are free to decide whether |
| * they want to call the inherited method. |
| * @param newInput the new input of this viewer, or <code>null</code> if there is no new input |
| * @param oldInput the old input element, or <code>null</code> if there was previously no input |
| * @return <code>true</code> if saving was successful, or if the user didn't want to save (by pressing 'NO' in the confirmation dialog). |
| * @since 2.0 |
| */ |
| protected boolean doSave(Object newInput, Object oldInput) { |
| // before setting the new input we have to save the old |
| if (isLeftDirty() || isRightDirty()) { |
| if (Utilities.RUNNING_TESTS) { |
| if (Utilities.TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE) { |
| flushContent(oldInput, null); |
| } |
| } else if (fConfirmSave) { |
| // post alert |
| Shell shell= fComposite.getShell(); |
| |
| MessageDialog dialog= new MessageDialog(shell, |
| Utilities.getString(getResourceBundle(), "saveDialog.title"), //$NON-NLS-1$ |
| null, // accept the default window icon |
| Utilities.getString(getResourceBundle(), "saveDialog.message"), //$NON-NLS-1$ |
| MessageDialog.QUESTION, |
| new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, }, |
| 0); // default button index |
| |
| switch (dialog.open()) { // open returns index of pressed button |
| case 0: |
| flushContent(oldInput, null); |
| break; |
| case 1: |
| setLeftDirty(false); |
| setRightDirty(false); |
| break; |
| case 2: |
| throw new ViewerSwitchingCancelled(); |
| } |
| } else { |
| flushContent(oldInput, null); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Controls whether <code>doSave(Object, Object)</code> asks for confirmation before saving |
| * the old input with <code>saveContent(Object)</code>. |
| * @param enable a value of <code>true</code> enables confirmation |
| * @since 2.0 |
| */ |
| public void setConfirmSave(boolean enable) { |
| fConfirmSave= enable; |
| } |
| |
| @Override |
| public void refresh() { |
| internalRefresh(getInput()); |
| } |
| |
| private void internalRefresh(Object input) { |
| IMergeViewerContentProvider content= getMergeContentProvider(); |
| if (content != null) { |
| Object ancestor= content.getAncestorContent(input); |
| boolean oldFlag = fIsThreeWay; |
| if (Utilities.isHunk(input)) { |
| fIsThreeWay = true; |
| } else if (input instanceof ICompareInput) { |
| fIsThreeWay= (((ICompareInput) input).getKind() & Differencer.DIRECTION_MASK) != 0; |
| } else { |
| fIsThreeWay= ancestor != null; |
| } |
| |
| if (fAncestorItem != null) |
| fAncestorItem.setVisible(fIsThreeWay); |
| |
| if (fAncestorVisible && oldFlag != fIsThreeWay) |
| fComposite.layout(true); |
| |
| Object left= content.getLeftContent(input); |
| Object right= content.getRightContent(input); |
| updateContent(ancestor, left, right); |
| |
| updateHeader(); |
| if (Utilities.okToUse(fComposite) && Utilities.okToUse(fComposite.getParent())) { |
| ToolBarManager tbm = (ToolBarManager) getToolBarManager(fComposite.getParent()); |
| if (tbm != null ) { |
| updateToolItems(); |
| tbm.update(true); |
| tbm.getControl().getParent().layout(true); |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void hookControl(Control control) { |
| if (Policy.debugContentMergeViewer) { |
| logTrace("Attached dispose listener to control " + System.identityHashCode(control)); //$NON-NLS-1$ |
| } |
| super.hookControl(control); |
| } |
| |
| //---- layout & SWT control creation |
| |
| /** |
| * Builds the SWT controls for the three areas of a compare/merge viewer. |
| * <p> |
| * Calls the hooks <code>createControls</code> and <code>createToolItems</code> |
| * to let subclasses build the specific content areas and to add items to |
| * an enclosing toolbar. |
| * <p> |
| * This method must only be called in the constructor of subclasses. |
| * |
| * @param parent the parent control |
| * @return the new control |
| */ |
| protected final Control buildControl(Composite parent) { |
| fComposite= new Composite(parent, fStyles | SWT.LEFT_TO_RIGHT) { // We force a specific direction |
| @Override |
| public boolean setFocus() { |
| return ContentMergeViewer.this.handleSetFocus(); |
| } |
| }; |
| fComposite.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle()); |
| |
| hookControl(fComposite); // Hook help & dispose listener. |
| |
| fComposite.setLayout(new ContentMergeViewerLayout()); |
| if (Policy.debugContentMergeViewer) { |
| logTrace("Created composite " + System.identityHashCode(fComposite) + " with layout " //$NON-NLS-1$//$NON-NLS-2$ |
| + System.identityHashCode(fComposite.getLayout())); |
| logStackTrace(); |
| } |
| |
| int style= SWT.SHADOW_OUT; |
| fAncestorLabel= new CLabel(fComposite, style | Window.getDefaultOrientation()); |
| |
| fLeftLabel= new CLabel(fComposite, style | Window.getDefaultOrientation()); |
| if (Policy.debugContentMergeViewer) { |
| logTrace("fLeftLabel initialized"); //$NON-NLS-1$ |
| logStackTrace(); |
| } |
| |
| fLeftLabelSet= true; |
| new Resizer(fLeftLabel, VERTICAL); |
| |
| fDirectionLabel= new CLabel(fComposite, style); |
| fDirectionLabel.setAlignment(SWT.CENTER); |
| new Resizer(fDirectionLabel, HORIZONTAL | VERTICAL); |
| |
| fRightLabel= new CLabel(fComposite, style | Window.getDefaultOrientation()); |
| new Resizer(fRightLabel, VERTICAL); |
| |
| if (fCenter == null || fCenter.isDisposed()) |
| fCenter= createCenterControl(fComposite); |
| |
| createControls(fComposite); |
| |
| fHandlerService= CompareHandlerService.createFor(getCompareConfiguration().getContainer(), fComposite.getShell()); |
| |
| initializeToolbars(parent); |
| |
| return fComposite; |
| } |
| |
| /** |
| * Returns the toolbar manager for this viewer. |
| * |
| * Subclasses may extend this method and use either the toolbar manager |
| * provided by the inherited method by calling |
| * super.getToolBarManager(parent) or provide an alternate toolbar manager. |
| * |
| * @param parent |
| * a <code>Composite</code> or <code>null</code> |
| * @return a <code>IToolBarManager</code> |
| * @since 3.4 |
| */ |
| protected IToolBarManager getToolBarManager(Composite parent) { |
| return CompareViewerPane.getToolBarManager(parent); |
| } |
| |
| private void initializeToolbars(Composite parent) { |
| ToolBarManager tbm = (ToolBarManager) getToolBarManager(parent); |
| if (tbm != null) { |
| tbm.removeAll(); |
| |
| // Define groups. |
| tbm.add(new Separator("modes")); //$NON-NLS-1$ |
| tbm.add(new Separator("merge")); //$NON-NLS-1$ |
| tbm.add(new Separator("navigation")); //$NON-NLS-1$ |
| |
| copyLeftToRightItem= createCopyAction(true); |
| Utilities.initAction(copyLeftToRightItem.getAction(), getResourceBundle(), "action.CopyLeftToRight."); //$NON-NLS-1$ |
| tbm.appendToGroup("merge", copyLeftToRightItem); //$NON-NLS-1$ |
| fHandlerService.registerAction(copyLeftToRightItem.getAction(), "org.eclipse.compare.copyAllLeftToRight"); //$NON-NLS-1$ |
| |
| copyRightToLeftItem= createCopyAction(false); |
| Utilities.initAction(copyRightToLeftItem.getAction(), getResourceBundle(), "action.CopyRightToLeft."); //$NON-NLS-1$ |
| tbm.appendToGroup("merge", copyRightToLeftItem); //$NON-NLS-1$ |
| fHandlerService.registerAction(copyRightToLeftItem.getAction(), "org.eclipse.compare.copyAllRightToLeft"); //$NON-NLS-1$ |
| |
| if (getCompareConfiguration().isMirroringEnabled()) { |
| fSwitchLeftAndRight = new Action() { |
| @Override |
| public void run() { |
| IPreferenceStore preferences = getCompareConfiguration().getPreferenceStore(); |
| preferences.setValue(ComparePreferencePage.SWAPPED, !getCompareConfiguration().isMirrored()); |
| if (preferences instanceof IPersistentPreferenceStore) { |
| try { |
| ((IPersistentPreferenceStore) preferences).save(); |
| } catch (IOException e) { |
| CompareUIPlugin.log(e); |
| } |
| } |
| } |
| }; |
| Utilities.initAction(fSwitchLeftAndRight, getResourceBundle(), "action.SwitchLeftAndRight."); //$NON-NLS-1$ |
| tbm.appendToGroup("modes", fSwitchLeftAndRight); //$NON-NLS-1$ |
| } |
| |
| final ChangePropertyAction a= new ChangePropertyAction(fBundle, getCompareConfiguration(), "action.EnableAncestor.", ICompareUIConstants.PROP_ANCESTOR_VISIBLE); //$NON-NLS-1$ |
| a.setChecked(fAncestorVisible); |
| fAncestorItem= new ActionContributionItem(a); |
| fAncestorItem.setVisible(false); |
| tbm.appendToGroup("modes", fAncestorItem); //$NON-NLS-1$ |
| tbm.getControl().addDisposeListener(a); |
| |
| createToolItems(tbm); |
| updateToolItems(); |
| |
| tbm.update(true); |
| } |
| } |
| |
| private ActionContributionItem createCopyAction(boolean leftToRight) { |
| return new ActionContributionItem(new Action() { |
| @Override |
| public void run() { |
| copy(leftToRight); |
| } |
| }); |
| } |
| |
| /** |
| * Callback that is invoked when the control of this merge viewer is given focus. |
| * This method should return <code>true</code> if a particular widget was given focus |
| * and false otherwise. By default, <code>false</code> is returned. Subclasses may override. |
| * @return whether particular widget was given focus |
| * @since 3.3 |
| */ |
| protected boolean handleSetFocus() { |
| return false; |
| } |
| |
| /** |
| * Return the desired width of the center control. This width is used |
| * to calculate the values used to layout the ancestor, left and right sides. |
| * @return the desired width of the center control |
| * @see #handleResizeLeftRight(int, int, int, int, int, int) |
| * @see #handleResizeAncestor(int, int, int, int) |
| * @since 3.3 |
| */ |
| protected int getCenterWidth() { |
| return 3; |
| } |
| |
| /** |
| * Return whether the ancestor pane is visible or not. |
| * @return whether the ancestor pane is visible or not |
| * @since 3.3 |
| */ |
| protected boolean isAncestorVisible() { |
| return fAncestorVisible; |
| } |
| |
| /** |
| * Create the control that divides the left and right sides of the merge viewer. |
| * @param parent the parent composite |
| * @return the center control |
| * @since 3.3 |
| */ |
| protected Control createCenterControl(Composite parent) { |
| Sash sash= new Sash(parent, SWT.VERTICAL); |
| new Resizer(sash, HORIZONTAL); |
| return sash; |
| } |
| |
| /** |
| * Return the center control that divides the left and right sides of the merge viewer. |
| * This method returns the control that was created by calling {@link #createCenterControl(Composite)}. |
| * @see #createCenterControl(Composite) |
| * @return the center control |
| * @since 3.3 |
| */ |
| protected Control getCenterControl() { |
| return fCenter; |
| } |
| |
| @Override |
| public Control getControl() { |
| return fComposite; |
| } |
| |
| /** |
| * Called on the viewer disposal. |
| * Unregisters from the compare configuration. |
| * Clients may extend if they have to do additional cleanup. |
| * @see org.eclipse.jface.viewers.ContentViewer#handleDispose(org.eclipse.swt.events.DisposeEvent) |
| */ |
| @Override |
| protected void handleDispose(DisposeEvent event) { |
| if (fHandlerService != null) |
| fHandlerService.dispose(); |
| |
| Object input= getInput(); |
| if (input instanceof ICompareInput) { |
| ICompareContainer container = getCompareConfiguration().getContainer(); |
| container.removeCompareInputChangeListener((ICompareInput)input, fCompareInputChangeListener); |
| } |
| if (input != null) { |
| ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider(); |
| if (lp != null) |
| lp.removeListener(labelChangeListener); |
| } |
| |
| if (fPropertyChangeListener != null) { |
| fCompareConfiguration.removePropertyChangeListener(fPropertyChangeListener); |
| fPropertyChangeListener= null; |
| } |
| |
| fAncestorLabel= null; |
| fLeftLabel= null; |
| if (Policy.debugContentMergeViewer) { |
| logTrace("handleDispose(...) - fLeftLabel = null. event.widget = " + System.identityHashCode(event.widget)); //$NON-NLS-1$ |
| logStackTrace(); |
| } |
| fDirectionLabel= null; |
| fRightLabel= null; |
| fCenter= null; |
| |
| if (fRightArrow != null) { |
| fRightArrow.dispose(); |
| fRightArrow= null; |
| } |
| if (fLeftArrow != null) { |
| fLeftArrow.dispose(); |
| fLeftArrow= null; |
| } |
| if (fBothArrow != null) { |
| fBothArrow.dispose(); |
| fBothArrow= null; |
| } |
| |
| if (fNormalCursor != null) { |
| fNormalCursor.dispose(); |
| fNormalCursor= null; |
| } |
| if (fHSashCursor != null) { |
| fHSashCursor.dispose(); |
| fHSashCursor= null; |
| } |
| if (fVSashCursor != null) { |
| fVSashCursor.dispose(); |
| fVSashCursor= null; |
| } |
| if (fHVSashCursor != null) { |
| fHVSashCursor.dispose(); |
| fHVSashCursor= null; |
| } |
| |
| super.handleDispose(event); |
| } |
| |
| /** |
| * Updates the enabled state of the toolbar items. |
| * <p> |
| * This method is called whenever the state of the items needs updating. |
| * <p> |
| * Subclasses may extend this method, although this is generally not required. |
| */ |
| protected void updateToolItems() { |
| IMergeViewerContentProvider content= getMergeContentProvider(); |
| |
| Object input= getInput(); |
| |
| if (copyLeftToRightItem != null) { |
| boolean rightEditable = content.isRightEditable(input); |
| copyLeftToRightItem.setVisible(rightEditable); |
| copyLeftToRightItem.getAction().setEnabled(rightEditable); |
| } |
| |
| if (copyRightToLeftItem != null) { |
| boolean leftEditable = content.isLeftEditable(input); |
| copyRightToLeftItem.setVisible(leftEditable); |
| copyRightToLeftItem.getAction().setEnabled(leftEditable); |
| } |
| |
| if (fSwitchLeftAndRight != null) { |
| fSwitchLeftAndRight.setChecked(getCompareConfiguration().isMirrored()); |
| } |
| } |
| |
| /** |
| * Updates the headers of the three areas |
| * by querying the content provider for a name and image for |
| * the three sides of the input object. |
| * <p> |
| * This method is called whenever the header must be updated. |
| * <p> |
| * Subclasses may extend this method, although this is generally not required. |
| */ |
| protected void updateHeader() { |
| IMergeViewerContentProvider content= getMergeContentProvider(); |
| Object input= getInput(); |
| |
| // Only change a label if there is a new label available |
| if (fAncestorLabel != null) { |
| Image ancestorImage = content.getAncestorImage(input); |
| if (ancestorImage != null) |
| fAncestorLabel.setImage(ancestorImage); |
| String ancestorLabel = content.getAncestorLabel(input); |
| if (ancestorLabel != null) |
| fAncestorLabel.setText(LegacyActionTools.escapeMnemonics(TextProcessor.process(ancestorLabel))); |
| } |
| if (fLeftLabel != null) { |
| Image leftImage = content.getLeftImage(input); |
| if (leftImage != null) |
| fLeftLabel.setImage(leftImage); |
| String leftLabel = content.getLeftLabel(input); |
| if (leftLabel != null) |
| fLeftLabel.setText(LegacyActionTools.escapeMnemonics(leftLabel)); |
| } |
| if (fRightLabel != null) { |
| Image rightImage = content.getRightImage(input); |
| if (rightImage != null) |
| fRightLabel.setImage(rightImage); |
| String rightLabel = content.getRightLabel(input); |
| if (rightLabel != null) |
| fRightLabel.setText(LegacyActionTools.escapeMnemonics(rightLabel)); |
| } |
| } |
| |
| /** |
| * Calculates the height of the header. |
| */ |
| /* package */ int getHeaderHeight() { |
| int headerHeight= fLeftLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y; |
| headerHeight= Math.max(headerHeight, fDirectionLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y); |
| return headerHeight; |
| } |
| |
| //---- dirty state & saving state |
| |
| @Override |
| public void addPropertyChangeListener(IPropertyChangeListener listener) { |
| if (fListenerList == null) |
| fListenerList= new ListenerList<>(); |
| fListenerList.add(listener); |
| } |
| |
| @Override |
| public void removePropertyChangeListener(IPropertyChangeListener listener) { |
| if (fListenerList != null) { |
| fListenerList.remove(listener); |
| if (fListenerList.isEmpty()) |
| fListenerList= null; |
| } |
| } |
| |
| private void fireDirtyState(boolean state) { |
| Utilities.firePropertyChange(fListenerList, this, CompareEditorInput.DIRTY_STATE, null, Boolean.valueOf(state)); |
| } |
| |
| /** |
| * Sets the dirty state of the left side of this viewer. |
| * If the new value differs from the old |
| * all registered listener are notified with |
| * a <code>PropertyChangeEvent</code> with the |
| * property name <code>CompareEditorInput.DIRTY_STATE</code>. |
| * |
| * @param dirty the state of the left side dirty flag |
| */ |
| protected void setLeftDirty(boolean dirty) { |
| if (isLeftDirty() != dirty) { |
| fIsLeftDirty = dirty; |
| // Always fire the event if the dirty state has changed |
| fireDirtyState(dirty); |
| } |
| } |
| |
| /** |
| * Sets the dirty state of the right side of this viewer. |
| * If the new value differs from the old |
| * all registered listener are notified with |
| * a <code>PropertyChangeEvent</code> with the |
| * property name <code>CompareEditorInput.DIRTY_STATE</code>. |
| * |
| * @param dirty the state of the right side dirty flag |
| */ |
| protected void setRightDirty(boolean dirty) { |
| if (isRightDirty() != dirty) { |
| fIsRightDirty = dirty; |
| // Always fire the event if the dirty state has changed |
| fireDirtyState(dirty); |
| } |
| } |
| |
| /** |
| * Method from the old internal <code>ISavable</code> interface |
| * Save the viewers's content. |
| * Note: this method is for internal use only. Clients should not call this method. |
| * |
| * @param monitor a progress monitor |
| * @throws CoreException |
| * @deprecated use {@link IFlushable#flush(IProgressMonitor)}. |
| */ |
| @Deprecated |
| public void save(IProgressMonitor monitor) throws CoreException { |
| flush(monitor); |
| } |
| |
| /** |
| * Flush any modifications made in the viewer into the compare input. This method |
| * calls {@link #flushContent(Object, IProgressMonitor)} with the compare input |
| * of the viewer as the first parameter. |
| * |
| * @param monitor a progress monitor |
| * @see org.eclipse.compare.contentmergeviewer.IFlushable#flush(org.eclipse.core.runtime.IProgressMonitor) |
| * @since 3.3 |
| */ |
| @Override |
| public final void flush(IProgressMonitor monitor) { |
| flushContent(getInput(), monitor); |
| } |
| |
| /** |
| * Flushes the modified content back to input elements via the content provider. |
| * The provided input may be the current input of the viewer or it may be |
| * the previous input (i.e. this method may be called to flush modified content |
| * during an input change). |
| * |
| * @param input the compare input |
| * @param monitor a progress monitor or <code>null</code> if the method |
| * was call from a place where a progress monitor was not available. |
| * @since 3.3 |
| */ |
| protected void flushContent(Object input, IProgressMonitor monitor) { |
| flushLeftSide(input, monitor); |
| flushRightSide(input, monitor); |
| } |
| |
| void flushLeftSide(Object input, IProgressMonitor monitor) { |
| IMergeViewerContentProvider content = (IMergeViewerContentProvider) getContentProvider(); |
| |
| boolean rightEmpty = content.getRightContent(input) == null; |
| |
| if (getCompareConfiguration().isLeftEditable() && isLeftDirty()) { |
| byte[] bytes = getContents(true); |
| if (rightEmpty && bytes != null && bytes.length == 0) |
| bytes = null; |
| setLeftDirty(false); |
| content.saveLeftContent(input, bytes); |
| } |
| } |
| |
| void flushRightSide(Object input, IProgressMonitor monitor) { |
| IMergeViewerContentProvider content = (IMergeViewerContentProvider) getContentProvider(); |
| |
| boolean leftEmpty = content.getLeftContent(input) == null; |
| |
| if (getCompareConfiguration().isRightEditable() && isRightDirty()) { |
| byte[] bytes = getContents(false); |
| if (leftEmpty && bytes != null && bytes.length == 0) |
| bytes = null; |
| setRightDirty(false); |
| content.saveRightContent(input, bytes); |
| } |
| } |
| |
| /** |
| * @param monitor |
| * @noreference This method is not intended to be referenced by clients. |
| */ |
| @Override |
| public void flushLeft(IProgressMonitor monitor) { |
| flushLeftSide(getInput(), monitor); |
| } |
| |
| /** |
| * @param monitor |
| * @noreference This method is not intended to be referenced by clients. |
| */ |
| @Override |
| public void flushRight(IProgressMonitor monitor) { |
| flushRightSide(getInput(), monitor); |
| } |
| |
| /** |
| * Return the dirty state of the right side of this viewer. |
| * @return the dirty state of the right side of this viewer |
| * @since 3.3 |
| */ |
| protected boolean isRightDirty() { |
| return fIsRightDirty; |
| } |
| |
| /** |
| * @return the dirty state of the right side of this viewer |
| * @since 3.7 |
| * @noreference This method is not intended to be referenced by clients. |
| */ |
| public boolean internalIsRightDirty() { |
| return isRightDirty(); |
| } |
| |
| /** |
| * Return the dirty state of the left side of this viewer. |
| * @return the dirty state of the left side of this viewer |
| * @since 3.3 |
| */ |
| protected boolean isLeftDirty() { |
| return fIsLeftDirty; |
| } |
| |
| /** |
| * @return the dirty state of the left side of this viewer |
| * @since 3.7 |
| * @noreference This method is not intended to be referenced by clients. |
| */ |
| public boolean internalIsLeftDirty() { |
| return isLeftDirty(); |
| } |
| |
| /** |
| * Handle a change to the given input reported from an {@link org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener}. |
| * This class registers a listener with its input and reports any change events through |
| * this method. By default, this method prompts for any unsaved changes and then refreshes |
| * the viewer. Subclasses may override. |
| * @since 3.3 |
| */ |
| protected void handleCompareInputChange() { |
| // Before setting the new input we have to save the old. |
| Object input = getInput(); |
| if (!isSaving() && (isLeftDirty() || isRightDirty())) { |
| |
| if (Utilities.RUNNING_TESTS) { |
| if (Utilities.TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE) { |
| flushContent(input, null); |
| } |
| } else { |
| // post alert |
| Shell shell= fComposite.getShell(); |
| |
| MessageDialog dialog= new MessageDialog(shell, |
| CompareMessages.ContentMergeViewer_resource_changed_title, |
| null, // accept the default window icon |
| CompareMessages.ContentMergeViewer_resource_changed_description, |
| MessageDialog.QUESTION, |
| new String[] { |
| IDialogConstants.YES_LABEL, // 0 |
| IDialogConstants.NO_LABEL, // 1 |
| }, |
| 0); // default button index |
| |
| switch (dialog.open()) { // open returns index of pressed button |
| case 0: |
| flushContent(input, null); |
| break; |
| case 1: |
| setLeftDirty(false); |
| setRightDirty(false); |
| break; |
| } |
| } |
| } |
| if (isSaving() && (isLeftDirty() || isRightDirty())) { |
| return; // Do not refresh until saving both sides is complete. |
| } |
| refresh(); |
| } |
| |
| CompareHandlerService getCompareHandlerService() { |
| return fHandlerService; |
| } |
| |
| /** |
| * @return true if any of the Saveables is being saved |
| */ |
| private boolean isSaving() { |
| ICompareContainer container = fCompareConfiguration.getContainer(); |
| ISaveablesSource source = null; |
| if (container instanceof ISaveablesSource) { |
| source = (ISaveablesSource) container; |
| } else { |
| IWorkbenchPart part = container.getWorkbenchPart(); |
| if (part instanceof ISaveablesSource) { |
| source = (ISaveablesSource) part; |
| } |
| } |
| if (source != null) { |
| Saveable[] saveables = source.getSaveables(); |
| for (int i = 0; i < saveables.length; i++) { |
| if (saveables[i] instanceof ISavingSaveable) { |
| ISavingSaveable saveable = (ISavingSaveable) saveables[i]; |
| if (saveable.isSaving()) |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * If the inputs are mirrored, this asks the right model value. |
| * |
| * @return true if the left viewer is editable |
| * @since 3.7 |
| */ |
| protected boolean isLeftEditable() { |
| return fCompareConfiguration.isMirrored() ? fCompareConfiguration.isRightEditable() : fCompareConfiguration.isLeftEditable(); |
| } |
| |
| /** |
| * If the inputs are mirrored, this asks the left model value. |
| * |
| * @return true if the right viewer is editable |
| * @since 3.7 |
| */ |
| protected boolean isRightEditable() { |
| return fCompareConfiguration.isMirrored() ? fCompareConfiguration.isLeftEditable() : fCompareConfiguration.isRightEditable(); |
| } |
| } |