Java tutorial
/******************************************************************************* * Copyright (c) 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.ui.compare; import java.util.ResourceBundle; import org.eclipse.compare.*; import org.eclipse.compare.contentmergeviewer.IFlushable; import org.eclipse.compare.contentmergeviewer.IMergeViewerContentProvider; import org.eclipse.compare.contentmergeviewer.TextMergeViewer; import org.eclipse.compare.internal.*; 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.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.*; import org.eclipse.jface.window.Window; import org.eclipse.osgi.util.TextProcessor; import org.eclipse.php.internal.ui.PHPUIMessages; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CLabel; import org.eclipse.swt.events.*; 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.*; /** * 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 { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=330672 org.eclipse.compare.contentmergeviewer.ContentMergeViewer cmv; class SaveAction extends MergeViewerAction { SaveAction(boolean left) { super(true, false, false); setText(PHPUIMessages.ContentMergeViewer_0); setToolTipText(PHPUIMessages.ContentMergeViewer_1); setDescription(PHPUIMessages.ContentMergeViewer_2); } public void run() { flush(null); } } /* 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 MergeViewerAction fLeftSaveAction; MergeViewerAction fRightSaveAction; 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); fLeftSaveAction = new SaveAction(true); fLeftSaveAction.setEnabled(false); fRightSaveAction = new SaveAction(false); fRightSaveAction.setEnabled(false); // this is used to update the dirty status,if we use // org.eclipse.php.internal.ui.compare.ContentMergeViewer,we will get a // ClassCastException cmv = new org.eclipse.compare.contentmergeviewer.ContentMergeViewer(fStyles, fBundle, fCompareConfiguration) { @Override protected void createControls(Composite composite) { } @Override protected void handleResizeAncestor(int x, int y, int width, int height) { } @Override protected void handleResizeLeftRight(int x, int y, int leftWidth, int centerWidth, int rightWidth, int height) { } @Override protected void updateContent(Object ancestor, Object left, Object right) { } @Override protected void copy(boolean leftToRight) { } @Override protected byte[] getContents(boolean left) { return null; } @Override public boolean internalIsLeftDirty() { return ContentMergeViewer.this.isLeftDirty(); } @Override public boolean internalIsRightDirty() { return ContentMergeViewer.this.isRightDirty(); } }; } // ---- 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) { return; } 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); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=453799 // Content provider may be disposed after call to updateContent() content = getMergeContentProvider(); if (content == null) { return; } updateHeader(); ToolBarManager tbm = CompareViewerPane.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; } private void initializeToolbars(Composite parent) { ToolBarManager tbm = CompareViewerPane.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; } 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(); if (content == null) { return; } 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(); if (content == null) { return; } 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(TextProcessor.process(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(TextProcessor.process(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, cmv, 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) { fLeftSaveAction.setEnabled(dirty); // Only fire the event if the combined dirty state has changed if ((!isRightDirty() && !isLeftDirty()) || (!isRightDirty() && isLeftDirty())) 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) { fRightSaveAction.setEnabled(dirty); // Only fire the event if the combined dirty state has changed if ((!isRightDirty() && !isLeftDirty()) || (isRightDirty() && !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 flushContentOld(Object input, IProgressMonitor monitor) { // write back modified contents IMergeViewerContentProvider content = (IMergeViewerContentProvider) getContentProvider(); if (content == null) { return; } 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); } } protected void flushContent(Object input, IProgressMonitor monitor) { flushLeftSide(input, monitor); flushRightSide(input, monitor); } void flushLeftSide(Object input, IProgressMonitor monitor) { IMergeViewerContentProvider content = (IMergeViewerContentProvider) getContentProvider(); if (content == null) { return; } 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(); if (content == null) { return; } 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 fRightSaveAction.isEnabled(); } /** * 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 fLeftSaveAction.isEnabled(); } /** * 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. Subclasses may override. * * @since 3.3 */ protected void handleCompareInputChange() { // before setting the new input we have to save the old Object input = getInput(); if (isLeftDirty() || isRightDirty()) { flushContent(input, null); } refresh(); } }