All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.compare.contentmergeviewer.ContentMergeViewer Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2000, 2015 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  - replace new Boolean with Boolean.valueOf - https://bugs.eclipse.org/470344
 *     Stefan Xenos  (Google) - bug 448968 - Add diagnostic logging
 *******************************************************************************/

package org.eclipse.compare.contentmergeviewer;

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.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.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.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.
 * 

* ContentMergeViewer *

    *
  • implements the overall layout and defines hooks so that subclasses * can easily provide an implementation for a specific content type, *
  • implements the UI for making the areas resizable, *
  • has an action for controlling whether the ancestor area is visible or not, *
  • has actions for copying one side of the input to the other side, *
  • tracks the dirty state of the left and right sides and send out notification * on state changes. *
* A ContentMergeViewer accesses its * model by means of a content provider which must implement the * IMergeViewerContentProvider interface. *

*

* Clients may wish to use the standard concrete subclass TextMergeViewer, * 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 { public Point computeSize(Composite c, int w, int h, boolean force) { return new Point(100, 100); } 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()); //$NON-NLS-2$ 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() { public void widgetDisposed(DisposeEvent e) { fControl= null; } } ); } public void mouseDoubleClick(MouseEvent e) { if ((fDirection & HORIZONTAL) != 0) fHSplit= -1; if ((fDirection & VERTICAL) != 0) fVSplit= VSPLIT; fComposite.layout(true); } 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; } public void mouseUp(MouseEvent e) { fIsDown= false; if (!fLiveResize) resize(e); } 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 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 Action fCopyLeftToRightAction; // copy from left to right private Action fCopyRightToLeftAction; // copy from right to left private boolean fIsLeftDirty; private boolean fIsRightDirty; private CompareHandlerService fHandlerService; // 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() { 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); setContentProvider(new MergeViewerContentProvider(cc)); fCompareInputChangeListener= new ICompareInputChangeListener() { public void compareInputChanged(ICompareInput input) { if (input == getInput()) { handleCompareInputChange(); } } }; // Make sure the compare configuration is not null if (cc == null) fCompareConfiguration = new CompareConfiguration(); else fCompareConfiguration= cc; fPropertyChangeListener= new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { ContentMergeViewer.this.handlePropertyChangeEvent(event); } }; fCompareConfiguration.addPropertyChangeListener(fPropertyChangeListener); fIsLeftDirty = false; fIsRightDirty = false; } private void logStackTrace() { new 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 * updateContent. * * @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 ToolBarManager. * It is called when this viewer is installed in its container and if the container * has a ToolBarManager. * The ContentMergeViewer 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 true, the left side is copied to the right side; * if false, 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 null can be returned. * * @param left if true, the byte contents of the left area is returned; * if false, the byte contents of the right area * @return the content as an array of bytes, or null */ 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, * or null if this viewer does not yet have a configuration. * * @return the compare configuration, or null if none */ protected CompareConfiguration getCompareConfiguration() { return fCompareConfiguration; } /** * The ContentMergeViewer implementation of this * ContentViewer method * checks to ensure that the content provider is an IMergeViewerContentProvider. * @param contentProvider the content provider to set. Must implement IMergeViewerContentProvider. */ public void setContentProvider(IContentProvider contentProvider) { Assert.isTrue(contentProvider instanceof IMergeViewerContentProvider); super.setContentProvider(contentProvider); } /* package */ IMergeViewerContentProvider getMergeContentProvider() { return (IMergeViewerContentProvider) getContentProvider(); } /** * The ContentMergeViewer implementation of this * Viewer method returns the empty selection. Subclasses may override. * @return empty selection. */ public ISelection getSelection() { return new ISelection() { public boolean isEmpty() { return true; } }; } /** * The ContentMergeViewer implementation of this * Viewer method does nothing. Subclasses may reimplement. * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean) */ 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; } } 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. *

* The ContentMergeViewer implementation of this Viewer * method tries to save the old input by calling doSave(...) and * then calls internalRefresh(...). * * @param input the new input of this viewer, or null if there is no new input * @param oldInput the old input element, or null if there was previously no input */ 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 Viewer method inputChanged * to save any unsaved changes of the old input. *

* The ContentMergeViewer implementation of this * method calls saveContent(...). If confirmation has been turned on * with setConfirmSave(true), a confirmation alert is posted before saving. *

* 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 null if there is no new input * @param oldInput the old input element, or null if there was previously no input * @return true 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 doSave(Object, Object) asks for confirmation before saving * the old input with saveContent(Object). * @param enable a value of true enables confirmation * @since 2.0 */ public void setConfirmSave(boolean enable) { fConfirmSave= enable; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.Viewer#refresh() */ 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); } } } } 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. *

* Calls the hooks createControls and createToolItems * to let subclasses build the specific content areas and to add items to * an enclosing toolbar. *

* 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 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 Composite or null * @return a IToolBarManager * @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$ CompareConfiguration cc= getCompareConfiguration(); if (cc.isRightEditable()) { fCopyLeftToRightAction= new Action() { public void run() { copy(true); } }; Utilities.initAction(fCopyLeftToRightAction, getResourceBundle(), "action.CopyLeftToRight."); //$NON-NLS-1$ tbm.appendToGroup("merge", fCopyLeftToRightAction); //$NON-NLS-1$ fHandlerService.registerAction(fCopyLeftToRightAction, "org.eclipse.compare.copyAllLeftToRight"); //$NON-NLS-1$ } if (cc.isLeftEditable()) { fCopyRightToLeftAction= new Action() { public void run() { copy(false); } }; Utilities.initAction(fCopyRightToLeftAction, getResourceBundle(), "action.CopyRightToLeft."); //$NON-NLS-1$ tbm.appendToGroup("merge", fCopyRightToLeftAction); //$NON-NLS-1$ fHandlerService.registerAction(fCopyRightToLeftAction, "org.eclipse.compare.copyAllRightToLeft"); //$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); } } /** * Callback that is invoked when the control of this merge viewer is given focus. * This method should return true if a particular widget was given focus * and false otherwise. By default, false 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; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.Viewer#getControl() */ 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) */ 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. *

* This method is called whenever the state of the items needs updating. *

* Subclasses may extend this method, although this is generally not required. */ protected void updateToolItems() { IMergeViewerContentProvider content= getMergeContentProvider(); Object input= getInput(); if (fCopyLeftToRightAction != null) { boolean enable= content.isRightEditable(input); // if (enable && input instanceof ICompareInput) { // ITypedElement e= ((ICompareInput) input).getLeft(); // if (e == null) // enable= false; // } fCopyLeftToRightAction.setEnabled(enable); } if (fCopyRightToLeftAction != null) { boolean enable= content.isLeftEditable(input); // if (enable && input instanceof ICompareInput) { // ITypedElement e= ((ICompareInput) input).getRight(); // if (e == null) // enable= false; // } fCopyRightToLeftAction.setEnabled(enable); } } /** * 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. *

* This method is called whenever the header must be updated. *

* 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 /* (non-Javadoc) * @see org.eclipse.compare.IPropertyChangeNotifier#addPropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener) */ public void addPropertyChangeListener(IPropertyChangeListener listener) { if (fListenerList == null) fListenerList= new ListenerList(); fListenerList.add(listener); } /* (non-Javadoc) * @see org.eclipse.compare.IPropertyChangeNotifier#removePropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener) */ 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 PropertyChangeEvent with the * property name CompareEditorInput.DIRTY_STATE. * * @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 PropertyChangeEvent with the * property name CompareEditorInput.DIRTY_STATE. * * @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 ISavable 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)}. */ 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 */ public final void flush(IProgressMonitor monitor) { flushContent(getInput(), monitor); } /** * Flush 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 null 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. */ public void flushLeft(IProgressMonitor monitor) { flushLeftSide(getInput(), monitor); } /** * @param monitor * @noreference This method is not intended to be referenced by clients. */ 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; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy