/******************************************************************************* * Copyright (c) 2000, 2010 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.compare.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.ICompareUIConstants; import org.eclipse.compare.internal.MergeViewerContentProvider; 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.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.IExecutionListener; import org.eclipse.core.commands.NotHandledException; 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.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.IWorkbenchCommandConstants; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; /** * 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 { /* 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) { // 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 boolean fIsSaving; private ICommandService fCommandService; private IExecutionListener fExecutionListener; private CompareHandlerService fHandlerService; // SWT widgets /* package */ Composite fComposite; private CLabel fAncestorLabel; private CLabel fLeftLabel; 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) { 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; fIsSaving = false; fCommandService = (ICommandService)PlatformUI.getWorkbench().getAdapter(ICommandService.class); if (fCommandService != null) { fCommandService.addExecutionListener(getExecutionListener()); } } private IExecutionListener getExecutionListener() { if (fExecutionListener == null) { fExecutionListener = new IExecutionListener() { public void preExecute(String commandId, ExecutionEvent event) { if (IWorkbenchCommandConstants.FILE_SAVE.equals(commandId) || IWorkbenchCommandConstants.FILE_SAVE_ALL.equals(commandId)) fIsSaving = true; } public void postExecuteSuccess(String commandId, Object returnValue) { if (IWorkbenchCommandConstants.FILE_SAVE.equals(commandId) || IWorkbenchCommandConstants.FILE_SAVE_ALL.equals(commandId)) fIsSaving= false; } public void postExecuteFailure(String commandId, ExecutionException exception) { if (IWorkbenchCommandConstants.FILE_SAVE.equals(commandId) || IWorkbenchCommandConstants.FILE_SAVE_ALL.equals(commandId)) fIsSaving= false; } public void notHandled(String commandId, NotHandledException exception) { // not needed } }; } return fExecutionListener; } //---- 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, * or <code>null</code> if this viewer does not yet have a configuration. * * @return the compare configuration, or <code>null</code> if none */ 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. */ public void setContentProvider(IContentProvider contentProvider) { Assert.isTrue(contentProvider instanceof IMergeViewerContentProvider); super.setContentProvider(contentProvider); } /* 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. */ public ISelection getSelection() { return new ISelection() { 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) */ 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, new Boolean(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 */ 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; } /* (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); } } } } //---- 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 public boolean setFocus() { return ContentMergeViewer.this.handleSetFocus(); } }; fComposite.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle()); hookControl(fComposite); // hook help & dispose listener fComposite.setLayout(new ContentMergeViewerLayout()); int style= SWT.SHADOW_OUT; fAncestorLabel= new CLabel(fComposite, style | Window.getDefaultOrientation()); fLeftLabel= new CLabel(fComposite, style | Window.getDefaultOrientation()); 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$ 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 <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; } /* (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; 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; } if (fCommandService != null) { fCommandService.removeExecutionListener(fExecutionListener); fCommandService = null; fExecutionListener = 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 (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. * <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(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(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(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, new Boolean(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; // Only fire the event if the combined dirty state has changed if (!isRightDirty()) 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; // Only fire the event if the combined dirty state has changed if (!isLeftDirty()) 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)}. */ 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 <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) { // write back modified contents IMergeViewerContentProvider content= (IMergeViewerContentProvider) getContentProvider(); boolean leftEmpty= content.getLeftContent(input) == null; 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); } if (getCompareConfiguration().isRightEditable() && isRightDirty()) { byte[] bytes= getContents(false); if (leftEmpty && bytes != null && bytes.length == 0) bytes= null; setRightDirty(false); content.saveRightContent(input, bytes); } } /** * 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 left side of this viewer. * @return the dirty state of the left side of this viewer * @since 3.3 */ protected boolean isLeftDirty() { return fIsLeftDirty; } /** * 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 (!fIsSaving && (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; } } } refresh(); } CompareHandlerService getCompareHandlerService() { return fHandlerService; } }