KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > compare > contentmergeviewer > ContentMergeViewer


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11
12 package org.eclipse.compare.contentmergeviewer;
13
14 import java.util.ResourceBundle JavaDoc;
15
16 import org.eclipse.compare.*;
17 import org.eclipse.compare.internal.*;
18 import org.eclipse.compare.structuremergeviewer.*;
19 import org.eclipse.core.runtime.*;
20 import org.eclipse.jface.action.*;
21 import org.eclipse.jface.dialogs.IDialogConstants;
22 import org.eclipse.jface.dialogs.MessageDialog;
23 import org.eclipse.jface.util.IPropertyChangeListener;
24 import org.eclipse.jface.util.PropertyChangeEvent;
25 import org.eclipse.jface.viewers.*;
26 import org.eclipse.jface.window.Window;
27 import org.eclipse.osgi.util.TextProcessor;
28 import org.eclipse.swt.SWT;
29 import org.eclipse.swt.custom.CLabel;
30 import org.eclipse.swt.events.*;
31 import org.eclipse.swt.graphics.*;
32 import org.eclipse.swt.widgets.*;
33
34 /**
35  * An abstract compare and merge viewer with two side-by-side content areas
36  * and an optional content area for the ancestor. The implementation makes no
37  * assumptions about the content type.
38  * <p>
39  * <code>ContentMergeViewer</code>
40  * <ul>
41  * <li>implements the overall layout and defines hooks so that subclasses
42  * can easily provide an implementation for a specific content type,
43  * <li>implements the UI for making the areas resizable,
44  * <li>has an action for controlling whether the ancestor area is visible or not,
45  * <li>has actions for copying one side of the input to the other side,
46  * <li>tracks the dirty state of the left and right sides and send out notification
47  * on state changes.
48  * </ul>
49  * A <code>ContentMergeViewer</code> accesses its
50  * model by means of a content provider which must implement the
51  * <code>IMergeViewerContentProvider</code> interface.
52  * </p>
53  * <p>
54  * Clients may wish to use the standard concrete subclass <code>TextMergeViewer</code>,
55  * or define their own subclass.
56  *
57  * @see IMergeViewerContentProvider
58  * @see TextMergeViewer
59  */

60 public abstract class ContentMergeViewer extends ContentViewer
61                     implements IPropertyChangeNotifier, IFlushable {
62     
63     class SaveAction extends MergeViewerAction {
64                 
65         SaveAction(boolean left) {
66             super(true, false, false);
67             Utilities.initAction(this, getResourceBundle(), "action.save."); //$NON-NLS-1$
68
}
69             
70         public void run() {
71             flush(null);
72         }
73     }
74     
75     /* package */ static final int HORIZONTAL= 1;
76     /* package */ static final int VERTICAL= 2;
77     
78     static final double HSPLIT= 0.5;
79     static final double VSPLIT= 0.3;
80     
81     private class ContentMergeViewerLayout extends Layout {
82         
83         public Point computeSize(Composite c, int w, int h, boolean force) {
84             return new Point(100, 100);
85         }
86         
87         public void layout(Composite composite, boolean force) {
88             
89             // determine some derived sizes
90
int headerHeight= fLeftLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y;
91             Rectangle r= composite.getClientArea();
92             
93             int centerWidth= getCenterWidth();
94             int width1= (int)((r.width-centerWidth)*getHorizontalSplitRatio());
95             int width2= r.width-width1-centerWidth;
96             
97             int height1= 0;
98             int height2= 0;
99             if (fIsThreeWay && fAncestorVisible) {
100                 height1= (int)((r.height-(2*headerHeight))*fVSplit);
101                 height2= r.height-(2*headerHeight)-height1;
102             } else {
103                 height1= 0;
104                 height2= r.height-headerHeight;
105             }
106                             
107             int y= 0;
108             
109             if (fIsThreeWay && fAncestorVisible) {
110                 fAncestorLabel.setBounds(0, y, r.width, headerHeight);
111                 fAncestorLabel.setVisible(true);
112                 y+= headerHeight;
113                 handleResizeAncestor(0, y, r.width, height1);
114                 y+= height1;
115             } else {
116                 fAncestorLabel.setVisible(false);
117                 handleResizeAncestor(0, 0, 0, 0);
118             }
119             
120             fLeftLabel.getSize(); // without this resizing would not always work
121

122             if (centerWidth > 3) {
123                 fLeftLabel.setBounds(0, y, width1+1, headerHeight);
124                 fDirectionLabel.setVisible(true);
125                 fDirectionLabel.setBounds(width1+1, y, centerWidth-1, headerHeight);
126                 fRightLabel.setBounds(width1+centerWidth, y, width2, headerHeight);
127             } else {
128                 fLeftLabel.setBounds(0, y, width1, headerHeight);
129                 fDirectionLabel.setVisible(false);
130                 fRightLabel.setBounds(width1, y, r.width-width1, headerHeight);
131             }
132             
133             y+= headerHeight;
134             
135             if (fCenter != null && !fCenter.isDisposed())
136                 fCenter.setBounds(width1, y, centerWidth, height2);
137                     
138             handleResizeLeftRight(0, y, width1, centerWidth, width2, height2);
139         }
140
141         private double getHorizontalSplitRatio() {
142             if (fHSplit < 0) {
143                 Object JavaDoc input = getInput();
144                 if (input instanceof ICompareInput) {
145                     ICompareInput ci = (ICompareInput) input;
146                     if (ci.getLeft() == null)
147                         return 0.1;
148                     if (ci.getRight() == null)
149                         return 0.9;
150                 }
151                 return HSPLIT;
152             }
153             return fHSplit;
154         }
155     }
156
157     class Resizer extends MouseAdapter implements MouseMoveListener {
158                 
159         Control fControl;
160         int fX, fY;
161         int fWidth1, fWidth2;
162         int fHeight1, fHeight2;
163         int fDirection;
164         boolean fLiveResize;
165         boolean fIsDown;
166         
167         public Resizer(Control c, int dir) {
168             fDirection= dir;
169             fControl= c;
170             fLiveResize= !(fControl instanceof Sash);
171             updateCursor(c, dir);
172             fControl.addMouseListener(this);
173             fControl.addMouseMoveListener(this);
174             fControl.addDisposeListener(
175                 new DisposeListener() {
176                     public void widgetDisposed(DisposeEvent e) {
177                         fControl= null;
178                     }
179                 }
180             );
181         }
182                 
183         public void mouseDoubleClick(MouseEvent e) {
184             if ((fDirection & HORIZONTAL) != 0)
185                 fHSplit= -1;
186             if ((fDirection & VERTICAL) != 0)
187                 fVSplit= VSPLIT;
188             fComposite.layout(true);
189         }
190         
191         public void mouseDown(MouseEvent e) {
192             Composite parent= fControl.getParent();
193             
194             Point s= parent.getSize();
195             Point as= fAncestorLabel.getSize();
196             Point ys= fLeftLabel.getSize();
197             Point ms= fRightLabel.getSize();
198             
199             fWidth1= ys.x;
200             fWidth2= ms.x;
201             fHeight1= fLeftLabel.getLocation().y-as.y;
202             fHeight2= s.y-(fLeftLabel.getLocation().y+ys.y);
203             
204             fX= e.x;
205             fY= e.y;
206             fIsDown= true;
207         }
208         
209         public void mouseUp(MouseEvent e) {
210             fIsDown= false;
211             if (!fLiveResize)
212                 resize(e);
213         }
214         
215         public void mouseMove(MouseEvent e) {
216             if (fIsDown && fLiveResize)
217                 resize(e);
218         }
219         
220         private void resize(MouseEvent e) {
221             int dx= e.x-fX;
222             int dy= e.y-fY;
223         
224             int centerWidth= fCenter.getSize().x;
225
226             if (fWidth1 + dx > centerWidth && fWidth2 - dx > centerWidth) {
227                 fWidth1+= dx;
228                 fWidth2-= dx;
229                 if ((fDirection & HORIZONTAL) != 0)
230                     fHSplit= (double)fWidth1/(double)(fWidth1+fWidth2);
231             }
232             if (fHeight1 + dy > centerWidth && fHeight2 - dy > centerWidth) {
233                 fHeight1+= dy;
234                 fHeight2-= dy;
235                 if ((fDirection & VERTICAL) != 0)
236                     fVSplit= (double)fHeight1/(double)(fHeight1+fHeight2);
237             }
238
239             fComposite.layout(true);
240             fControl.getDisplay().update();
241         }
242     }
243
244     /** Style bits for top level composite */
245     private int fStyles;
246     private ResourceBundle JavaDoc fBundle;
247     private final CompareConfiguration fCompareConfiguration;
248     private IPropertyChangeListener fPropertyChangeListener;
249     private ICompareInputChangeListener fCompareInputChangeListener;
250     private ListenerList fListenerList;
251     boolean fConfirmSave= true;
252     
253     private double fHSplit= -1; // width ratio of left and right panes
254
private double fVSplit= VSPLIT; // height ratio of ancestor and bottom panes
255

256     private boolean fIsThreeWay; // whether their is an ancestor
257
private boolean fAncestorVisible; // whether the ancestor pane is visible
258
private ActionContributionItem fAncestorItem;
259     
260     private Action fCopyLeftToRightAction; // copy from left to right
261
private Action fCopyRightToLeftAction; // copy from right to left
262

263     MergeViewerAction fLeftSaveAction;
264     MergeViewerAction fRightSaveAction;
265     
266     private CompareHandlerService fHandlerService;
267
268     // SWT widgets
269
/* package */ Composite fComposite;
270     private CLabel fAncestorLabel;
271     private CLabel fLeftLabel;
272     private CLabel fRightLabel;
273     /* package */ CLabel fDirectionLabel;
274     /* package */ Control fCenter;
275         
276     //---- SWT resources to be disposed
277
private Image fRightArrow;
278     private Image fLeftArrow;
279     private Image fBothArrow;
280     Cursor fNormalCursor;
281     private Cursor fHSashCursor;
282     private Cursor fVSashCursor;
283     private Cursor fHVSashCursor;
284
285     private ILabelProviderListener labelChangeListener = new ILabelProviderListener() {
286         public void labelProviderChanged(LabelProviderChangedEvent event) {
287             Object JavaDoc[] elements = event.getElements();
288             for (int i = 0; i < elements.length; i++) {
289                 Object JavaDoc object = elements[i];
290                 if (object == getInput())
291                     updateHeader();
292             }
293         }
294     };
295
296     //---- end
297

298     /**
299      * Creates a new content merge viewer and initializes with a resource bundle and a
300      * configuration.
301      *
302      * @param style SWT style bits
303      * @param bundle the resource bundle
304      * @param cc the configuration object
305      */

306     protected ContentMergeViewer(int style, ResourceBundle JavaDoc bundle, CompareConfiguration cc) {
307         
308         fStyles= style & ~(SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT); // remove BIDI direction bits
309
fBundle= bundle;
310         
311         fAncestorVisible= Utilities.getBoolean(cc, ICompareUIConstants.PROP_ANCESTOR_VISIBLE, fAncestorVisible);
312         fConfirmSave= Utilities.getBoolean(cc, CompareEditor.CONFIRM_SAVE_PROPERTY, fConfirmSave);
313
314         setContentProvider(new MergeViewerContentProvider(cc));
315         
316         fCompareInputChangeListener= new ICompareInputChangeListener() {
317             public void compareInputChanged(ICompareInput input) {
318                 if (input == getInput()) {
319                     handleCompareInputChange();
320                 }
321             }
322         };
323         
324         // Make sure the compare configuration is not null
325
if (cc == null)
326             fCompareConfiguration = new CompareConfiguration();
327         else
328             fCompareConfiguration= cc;
329         fPropertyChangeListener= new IPropertyChangeListener() {
330             public void propertyChange(PropertyChangeEvent event) {
331                 ContentMergeViewer.this.handlePropertyChangeEvent(event);
332             }
333         };
334         fCompareConfiguration.addPropertyChangeListener(fPropertyChangeListener);
335             
336         fLeftSaveAction= new SaveAction(true);
337         fLeftSaveAction.setEnabled(false);
338         fRightSaveAction= new SaveAction(false);
339         fRightSaveAction.setEnabled(false);
340         
341     }
342     
343     //---- hooks ---------------------
344

345     /**
346      * Returns the viewer's name.
347      *
348      * @return the viewer's name
349      */

350     public String JavaDoc getTitle() {
351         return Utilities.getString(getResourceBundle(), "title"); //$NON-NLS-1$
352
}
353     
354     /**
355      * Creates the SWT controls for the ancestor, left, and right
356      * content areas of this compare viewer.
357      * Implementations typically hold onto the controls
358      * so that they can be initialized with the input objects in method
359      * <code>updateContent</code>.
360      *
361      * @param composite the container for the three areas
362      */

363     abstract protected void createControls(Composite composite);
364
365     /**
366      * Lays out the ancestor area of the compare viewer.
367      * It is called whenever the viewer is resized or when the sashes between
368      * the areas are moved to adjust the size of the areas.
369      *
370      * @param x the horizontal position of the ancestor area within its container
371      * @param y the vertical position of the ancestor area within its container
372      * @param width the width of the ancestor area
373      * @param height the height of the ancestor area
374      */

375     abstract protected void handleResizeAncestor(int x, int y, int width, int height);
376     
377     /**
378      * Lays out the left and right areas of the compare viewer.
379      * It is called whenever the viewer is resized or when the sashes between
380      * the areas are moved to adjust the size of the areas.
381      *
382      * @param x the horizontal position of the left area within its container
383      * @param y the vertical position of the left and right area within its container
384      * @param leftWidth the width of the left area
385      * @param centerWidth the width of the gap between the left and right areas
386      * @param rightWidth the width of the right area
387      * @param height the height of the left and right areas
388      */

389     abstract protected void handleResizeLeftRight(int x, int y, int leftWidth, int centerWidth,
390             int rightWidth, int height);
391
392     /**
393      * Contributes items to the given <code>ToolBarManager</code>.
394      * It is called when this viewer is installed in its container and if the container
395      * has a <code>ToolBarManager</code>.
396      * The <code>ContentMergeViewer</code> implementation of this method does nothing.
397      * Subclasses may reimplement.
398      *
399      * @param toolBarManager the toolbar manager to contribute to
400      */

401     protected void createToolItems(ToolBarManager toolBarManager) {
402         // empty implementation
403
}
404
405     /**
406      * Initializes the controls of the three content areas with the given input objects.
407      *
408      * @param ancestor the input for the ancestor area
409      * @param left the input for the left area
410      * @param right the input for the right area
411      */

412     abstract protected void updateContent(Object JavaDoc ancestor, Object JavaDoc left, Object JavaDoc right);
413         
414     /**
415      * Copies the content of one side to the other side.
416      * Called from the (internal) actions for copying the sides of the viewer's input object.
417      *
418      * @param leftToRight if <code>true</code>, the left side is copied to the right side;
419      * if <code>false</code>, the right side is copied to the left side
420      */

421     abstract protected void copy(boolean leftToRight);
422
423     /**
424      * Returns the byte contents of the left or right side. If the viewer
425      * has no editable content <code>null</code> can be returned.
426      *
427      * @param left if <code>true</code>, the byte contents of the left area is returned;
428      * if <code>false</code>, the byte contents of the right area
429      * @return the content as an array of bytes, or <code>null</code>
430      */

431     abstract protected byte[] getContents(boolean left);
432
433     //----------------------------
434

435     /**
436      * Returns the resource bundle of this viewer.
437      *
438      * @return the resource bundle
439      */

440     protected ResourceBundle JavaDoc getResourceBundle() {
441         return fBundle;
442     }
443     
444     /**
445      * Returns the compare configuration of this viewer,
446      * or <code>null</code> if this viewer does not yet have a configuration.
447      *
448      * @return the compare configuration, or <code>null</code> if none
449      */

450     protected CompareConfiguration getCompareConfiguration() {
451         return fCompareConfiguration;
452     }
453     
454     /**
455      * The <code>ContentMergeViewer</code> implementation of this
456      * <code>ContentViewer</code> method
457      * checks to ensure that the content provider is an <code>IMergeViewerContentProvider</code>.
458      * @param contentProvider the content provider to set. Must implement IMergeViewerContentProvider.
459      */

460     public void setContentProvider(IContentProvider contentProvider) {
461         Assert.isTrue(contentProvider instanceof IMergeViewerContentProvider);
462         super.setContentProvider(contentProvider);
463     }
464
465     /* package */ IMergeViewerContentProvider getMergeContentProvider() {
466         return (IMergeViewerContentProvider) getContentProvider();
467     }
468
469     /**
470      * The <code>ContentMergeViewer</code> implementation of this
471      * <code>Viewer</code> method returns the empty selection. Subclasses may override.
472      * @return empty selection.
473      */

474     public ISelection getSelection() {
475         return new ISelection() {
476             public boolean isEmpty() {
477                 return true;
478             }
479         };
480     }
481     
482     /**
483      * The <code>ContentMergeViewer</code> implementation of this
484      * <code>Viewer</code> method does nothing. Subclasses may reimplement.
485      * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean)
486      */

487     public void setSelection(ISelection selection, boolean reveal) {
488         // empty implementation
489
}
490
491     /**
492      * Callback that is invoked when a property in the compare configuration
493      * ({@link #getCompareConfiguration()} changes.
494      * @param event the property change event
495      * @since 3.3
496      */

497     protected void handlePropertyChangeEvent(PropertyChangeEvent event) {
498         
499         String JavaDoc key= event.getProperty();
500
501         if (key.equals(ICompareUIConstants.PROP_ANCESTOR_VISIBLE)) {
502             fAncestorVisible= Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_ANCESTOR_VISIBLE, fAncestorVisible);
503             fComposite.layout(true);
504             
505             updateCursor(fLeftLabel, VERTICAL);
506             updateCursor(fDirectionLabel, HORIZONTAL | VERTICAL);
507             updateCursor(fRightLabel, VERTICAL);
508             
509             return;
510         }
511         
512         if (key.equals(ICompareUIConstants.PROP_IGNORE_ANCESTOR)) {
513             setAncestorVisibility(false, !Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_IGNORE_ANCESTOR, false));
514             return;
515         }
516     }
517     
518     void updateCursor(Control c, int dir) {
519         if (!(c instanceof Sash)) {
520             Cursor cursor= null;
521             switch (dir) {
522             case VERTICAL:
523                 if (fAncestorVisible) {
524                     if (fVSashCursor == null) fVSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZENS);
525                     cursor= fVSashCursor;
526                 } else {
527                     if (fNormalCursor == null) fNormalCursor= new Cursor(c.getDisplay(), SWT.CURSOR_ARROW);
528                     cursor= fNormalCursor;
529                 }
530                 break;
531             case HORIZONTAL:
532                 if (fHSashCursor == null) fHSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZEWE);
533                 cursor= fHSashCursor;
534                 break;
535             case VERTICAL + HORIZONTAL:
536                 if (fAncestorVisible) {
537                     if (fHVSashCursor == null) fHVSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZEALL);
538                     cursor= fHVSashCursor;
539                 } else {
540                     if (fHSashCursor == null) fHSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZEWE);
541                     cursor= fHSashCursor;
542                 }
543                 break;
544             }
545             if (cursor != null)
546                 c.setCursor(cursor);
547         }
548     }
549
550     private void setAncestorVisibility(boolean visible, boolean enabled) {
551         if (fAncestorItem != null) {
552             Action action= (Action) fAncestorItem.getAction();
553             if (action != null) {
554                 action.setChecked(visible);
555                 action.setEnabled(enabled);
556             }
557         }
558         getCompareConfiguration().setProperty(ICompareUIConstants.PROP_ANCESTOR_VISIBLE, new Boolean JavaDoc(visible));
559     }
560
561     //---- input
562

563     /**
564      * Return whether the input is a three-way comparison.
565      * @return whether the input is a three-way comparison
566      * @since 3.3
567      */

568     protected boolean isThreeWay() {
569         return fIsThreeWay;
570     }
571     
572     /**
573      * Internal hook method called when the input to this viewer is
574      * initially set or subsequently changed.
575      * <p>
576      * The <code>ContentMergeViewer</code> implementation of this <code>Viewer</code>
577      * method tries to save the old input by calling <code>doSave(...)</code> and
578      * then calls <code>internalRefresh(...)</code>.
579      *
580      * @param input the new input of this viewer, or <code>null</code> if there is no new input
581      * @param oldInput the old input element, or <code>null</code> if there was previously no input
582      */

583     protected final void inputChanged(Object JavaDoc input, Object JavaDoc oldInput) {
584         
585         if (input != oldInput && oldInput != null) {
586             ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider();
587             if (lp != null)
588                 lp.removeListener(labelChangeListener);
589         }
590         
591         if (input != oldInput && oldInput instanceof ICompareInput) {
592             ICompareContainer container = getCompareConfiguration().getContainer();
593             container.removeCompareInputChangeListener((ICompareInput)oldInput, fCompareInputChangeListener);
594         }
595         
596         boolean success= doSave(input, oldInput);
597         
598         if (input != oldInput && input instanceof ICompareInput) {
599             ICompareContainer container = getCompareConfiguration().getContainer();
600             container.addCompareInputChangeListener((ICompareInput)input, fCompareInputChangeListener);
601         }
602         
603         if (input != oldInput && input != null) {
604             ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider();
605             if (lp != null)
606                 lp.addListener(labelChangeListener);
607         }
608         
609         if (success) {
610             setLeftDirty(false);
611             setRightDirty(false);
612         }
613
614         if (input != oldInput)
615             internalRefresh(input);
616     }
617     
618     /**
619      * This method is called from the <code>Viewer</code> method <code>inputChanged</code>
620      * to save any unsaved changes of the old input.
621      * <p>
622      * The <code>ContentMergeViewer</code> implementation of this
623      * method calls <code>saveContent(...)</code>. If confirmation has been turned on
624      * with <code>setConfirmSave(true)</code>, a confirmation alert is posted before saving.
625      * </p>
626      * Clients can override this method and are free to decide whether
627      * they want to call the inherited method.
628      * @param newInput the new input of this viewer, or <code>null</code> if there is no new input
629      * @param oldInput the old input element, or <code>null</code> if there was previously no input
630      * @return <code>true</code> if saving was successful, or if the user didn't want to save (by pressing 'NO' in the confirmation dialog).
631      * @since 2.0
632      */

633     protected boolean doSave(Object JavaDoc newInput, Object JavaDoc oldInput) {
634         
635         // before setting the new input we have to save the old
636
if (isLeftDirty() || isRightDirty()) {
637             
638             
639             if (Utilities.RUNNING_TESTS) {
640                 if (Utilities.TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE) {
641                     flushContent(oldInput, null);
642                 }
643             } else if (fConfirmSave) {
644                 // post alert
645
Shell shell= fComposite.getShell();
646                 
647                 MessageDialog dialog= new MessageDialog(shell,
648                     Utilities.getString(getResourceBundle(), "saveDialog.title"), //$NON-NLS-1$
649
null, // accept the default window icon
650
Utilities.getString(getResourceBundle(), "saveDialog.message"), //$NON-NLS-1$
651
MessageDialog.QUESTION,
652                     new String JavaDoc[] {
653                         IDialogConstants.YES_LABEL,
654                         IDialogConstants.NO_LABEL,
655                     },
656                     0); // default button index
657

658                 switch (dialog.open()) { // open returns index of pressed button
659
case 0:
660                     flushContent(oldInput, null);
661                     break;
662                 case 1:
663                     setLeftDirty(false);
664                     setRightDirty(false);
665                     break;
666                 case 2:
667                     throw new ViewerSwitchingCancelled();
668                 }
669             } else
670                 flushContent(oldInput, null);
671             return true;
672         }
673         return false;
674     }
675         
676     /**
677      * Controls whether <code>doSave(Object, Object)</code> asks for confirmation before saving
678      * the old input with <code>saveContent(Object)</code>.
679      * @param enable a value of <code>true</code> enables confirmation
680      * @since 2.0
681      */

682     public void setConfirmSave(boolean enable) {
683         fConfirmSave= enable;
684     }
685     
686     /* (non-Javadoc)
687      * @see org.eclipse.jface.viewers.Viewer#refresh()
688      */

689     public void refresh() {
690         internalRefresh(getInput());
691     }
692     
693     private void internalRefresh(Object JavaDoc input) {
694         
695         IMergeViewerContentProvider content= getMergeContentProvider();
696         if (content != null) {
697             Object JavaDoc ancestor= content.getAncestorContent(input);
698             boolean oldFlag = fIsThreeWay;
699             if (Utilities.isHunk(input)) {
700                 fIsThreeWay = true;
701             } else if (input instanceof ICompareInput)
702                 fIsThreeWay= (((ICompareInput)input).getKind() & Differencer.DIRECTION_MASK) != 0;
703             else
704                 fIsThreeWay= ancestor != null;
705                 
706             if (fAncestorItem != null)
707                 fAncestorItem.setVisible(fIsThreeWay);
708             
709             if (fAncestorVisible && oldFlag != fIsThreeWay)
710                 fComposite.layout(true);
711             
712                                     
713             Object JavaDoc left= content.getLeftContent(input);
714             Object JavaDoc right= content.getRightContent(input);
715             updateContent(ancestor, left, right);
716             
717             updateHeader();
718             ToolBarManager tbm= CompareViewerPane.getToolBarManager(fComposite.getParent());
719             if (tbm != null ) {
720                 updateToolItems();
721                 tbm.update(true);
722                 tbm.getControl().getParent().layout(true);
723             }
724             
725         }
726     }
727     
728     //---- layout & SWT control creation
729

730     /**
731      * Builds the SWT controls for the three areas of a compare/merge viewer.
732      * <p>
733      * Calls the hooks <code>createControls</code> and <code>createToolItems</code>
734      * to let subclasses build the specific content areas and to add items to
735      * an enclosing toolbar.
736      * <p>
737      * This method must only be called in the constructor of subclasses.
738      *
739      * @param parent the parent control
740      * @return the new control
741      */

742     protected final Control buildControl(Composite parent) {
743                                             
744         fComposite= new Composite(parent, fStyles | SWT.LEFT_TO_RIGHT) { // we force a specific direction
745
public boolean setFocus() {
746                 return ContentMergeViewer.this.handleSetFocus();
747             }
748         };
749         fComposite.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle());
750         
751         hookControl(fComposite); // hook help & dispose listener
752

753         fComposite.setLayout(new ContentMergeViewerLayout());
754         
755         int style= SWT.SHADOW_OUT;
756         fAncestorLabel= new CLabel(fComposite, style | Window.getDefaultOrientation());
757         
758         fLeftLabel= new CLabel(fComposite, style | Window.getDefaultOrientation());
759         new Resizer(fLeftLabel, VERTICAL);
760         
761         fDirectionLabel= new CLabel(fComposite, style);
762         fDirectionLabel.setAlignment(SWT.CENTER);
763         new Resizer(fDirectionLabel, HORIZONTAL | VERTICAL);
764         
765         fRightLabel= new CLabel(fComposite, style | Window.getDefaultOrientation());
766         new Resizer(fRightLabel, VERTICAL);
767         
768         if (fCenter == null || fCenter.isDisposed())
769             fCenter= createCenterControl(fComposite);
770                 
771         createControls(fComposite);
772         
773         fHandlerService= CompareHandlerService.createFor(getCompareConfiguration().getContainer(), fComposite.getShell());
774                         
775         initializeToolbars(parent);
776     
777         return fComposite;
778     }
779
780     private void initializeToolbars(Composite parent) {
781         ToolBarManager tbm= CompareViewerPane.getToolBarManager(parent);
782         if (tbm != null) {
783             tbm.removeAll();
784             
785             // define groups
786
tbm.add(new Separator("modes")); //$NON-NLS-1$
787
tbm.add(new Separator("merge")); //$NON-NLS-1$
788
tbm.add(new Separator("navigation")); //$NON-NLS-1$
789

790             CompareConfiguration cc= getCompareConfiguration();
791         
792             if (cc.isRightEditable()) {
793                 fCopyLeftToRightAction=
794                     new Action() {
795                         public void run() {
796                             copy(true);
797                         }
798                     };
799                 Utilities.initAction(fCopyLeftToRightAction, getResourceBundle(), "action.CopyLeftToRight."); //$NON-NLS-1$
800
tbm.appendToGroup("merge", fCopyLeftToRightAction); //$NON-NLS-1$
801
fHandlerService.registerAction(fCopyLeftToRightAction, "org.eclipse.compare.copyAllLeftToRight"); //$NON-NLS-1$
802
}
803             
804             if (cc.isLeftEditable()) {
805                 fCopyRightToLeftAction=
806                     new Action() {
807                         public void run() {
808                             copy(false);
809                         }
810                     };
811                 Utilities.initAction(fCopyRightToLeftAction, getResourceBundle(), "action.CopyRightToLeft."); //$NON-NLS-1$
812
tbm.appendToGroup("merge", fCopyRightToLeftAction); //$NON-NLS-1$
813
fHandlerService.registerAction(fCopyRightToLeftAction, "org.eclipse.compare.copyAllRightToLeft"); //$NON-NLS-1$
814
}
815             
816             final ChangePropertyAction a= new ChangePropertyAction(fBundle, getCompareConfiguration(), "action.EnableAncestor.", ICompareUIConstants.PROP_ANCESTOR_VISIBLE); //$NON-NLS-1$
817
a.setChecked(fAncestorVisible);
818             fAncestorItem= new ActionContributionItem(a);
819             fAncestorItem.setVisible(false);
820             tbm.appendToGroup("modes", fAncestorItem); //$NON-NLS-1$
821
tbm.getControl().addDisposeListener(a);
822             
823             createToolItems(tbm);
824             updateToolItems();
825             
826             tbm.update(true);
827         }
828     }
829     
830     /**
831      * Callback that is invoked when the control of this merge viewer is given focus.
832      * This method should return <code>true</code> if a particular widget was given focus
833      * and false otherwise. By default, <code>false</code> is returned. Subclasses may override.
834      * @return whether particular widget was given focus
835      * @since 3.3
836      */

837     protected boolean handleSetFocus() {
838         return false;
839     }
840     
841     /**
842      * Return the desired width of the center control. This width is used
843      * to calculate the values used to layout the ancestor, left and right sides.
844      * @return the desired width of the center control
845      * @see #handleResizeLeftRight(int, int, int, int, int, int)
846      * @see #handleResizeAncestor(int, int, int, int)
847      * @since 3.3
848      */

849     protected int getCenterWidth() {
850         return 3;
851     }
852     
853     /**
854      * Return whether the ancestor pane is visible or not.
855      * @return whether the ancestor pane is visible or not
856      * @since 3.3
857      */

858     protected boolean isAncestorVisible() {
859         return fAncestorVisible;
860     }
861     
862     /**
863      * Create the control that divides the left and right sides of the merge viewer.
864      * @param parent the parent composite
865      * @return the center control
866      * @since 3.3
867      */

868     protected Control createCenterControl(Composite parent) {
869         Sash sash= new Sash(parent, SWT.VERTICAL);
870         new Resizer(sash, HORIZONTAL);
871         return sash;
872     }
873     
874     /**
875      * Return the center control that divides the left and right sides of the merge viewer.
876      * This method returns the control that was created by calling {@link #createCenterControl(Composite)}.
877      * @see #createCenterControl(Composite)
878      * @return the center control
879      * @since 3.3
880      */

881     protected Control getCenterControl() {
882         return fCenter;
883     }
884         
885     /* (non-Javadoc)
886      * @see org.eclipse.jface.viewers.Viewer#getControl()
887      */

888     public Control getControl() {
889         return fComposite;
890     }
891     
892     /**
893      * Called on the viewer disposal.
894      * Unregisters from the compare configuration.
895      * Clients may extend if they have to do additional cleanup.
896      * @see org.eclipse.jface.viewers.ContentViewer#handleDispose(org.eclipse.swt.events.DisposeEvent)
897      */

898     protected void handleDispose(DisposeEvent event) {
899         
900         if (fHandlerService != null)
901             fHandlerService.dispose();
902         
903         Object JavaDoc input= getInput();
904         if (input instanceof ICompareInput) {
905             ICompareContainer container = getCompareConfiguration().getContainer();
906             container.removeCompareInputChangeListener((ICompareInput)input, fCompareInputChangeListener);
907         }
908         if (input != null) {
909             ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider();
910             if (lp != null)
911                 lp.removeListener(labelChangeListener);
912         }
913         
914         if (fPropertyChangeListener != null) {
915             fCompareConfiguration.removePropertyChangeListener(fPropertyChangeListener);
916             fPropertyChangeListener= null;
917         }
918
919         fAncestorLabel= null;
920         fLeftLabel= null;
921         fDirectionLabel= null;
922         fRightLabel= null;
923         fCenter= null;
924                 
925         if (fRightArrow != null) {
926             fRightArrow.dispose();
927             fRightArrow= null;
928         }
929         if (fLeftArrow != null) {
930             fLeftArrow.dispose();
931             fLeftArrow= null;
932         }
933         if (fBothArrow != null) {
934             fBothArrow.dispose();
935             fBothArrow= null;
936         }
937
938         if (fNormalCursor != null) {
939             fNormalCursor.dispose();
940             fNormalCursor= null;
941         }
942         if (fHSashCursor != null) {
943             fHSashCursor.dispose();
944             fHSashCursor= null;
945         }
946         if (fVSashCursor != null) {
947             fVSashCursor.dispose();
948             fVSashCursor= null;
949         }
950         if (fHVSashCursor != null) {
951             fHVSashCursor.dispose();
952             fHVSashCursor= null;
953         }
954         
955         super.handleDispose(event);
956     }
957     
958     /**
959      * Updates the enabled state of the toolbar items.
960      * <p>
961      * This method is called whenever the state of the items needs updating.
962      * <p>
963      * Subclasses may extend this method, although this is generally not required.
964      */

965     protected void updateToolItems() {
966                                         
967         IMergeViewerContentProvider content= getMergeContentProvider();
968         
969         Object JavaDoc input= getInput();
970         
971         if (fCopyLeftToRightAction != null) {
972             boolean enable= content.isRightEditable(input);
973 // if (enable && input instanceof ICompareInput) {
974
// ITypedElement e= ((ICompareInput) input).getLeft();
975
// if (e == null)
976
// enable= false;
977
// }
978
fCopyLeftToRightAction.setEnabled(enable);
979         }
980         
981         if (fCopyRightToLeftAction != null) {
982             boolean enable= content.isLeftEditable(input);
983 // if (enable && input instanceof ICompareInput) {
984
// ITypedElement e= ((ICompareInput) input).getRight();
985
// if (e == null)
986
// enable= false;
987
// }
988
fCopyRightToLeftAction.setEnabled(enable);
989         }
990     }
991     
992     /**
993      * Updates the headers of the three areas
994      * by querying the content provider for a name and image for
995      * the three sides of the input object.
996      * <p>
997      * This method is called whenever the header must be updated.
998      * <p>
999      * Subclasses may extend this method, although this is generally not required.
1000     */

1001    protected void updateHeader() {
1002                        
1003        IMergeViewerContentProvider content= getMergeContentProvider();
1004        Object JavaDoc input= getInput();
1005
1006        // Only change a label if there is a new label available
1007
if (fAncestorLabel != null) {
1008            Image ancestorImage = content.getAncestorImage(input);
1009            if (ancestorImage != null)
1010                fAncestorLabel.setImage(ancestorImage);
1011            String JavaDoc ancestorLabel = content.getAncestorLabel(input);
1012            if (ancestorLabel != null)
1013                fAncestorLabel.setText(TextProcessor.process(ancestorLabel));
1014        }
1015        if (fLeftLabel != null) {
1016            Image leftImage = content.getLeftImage(input);
1017            if (leftImage != null)
1018                fLeftLabel.setImage(leftImage);
1019            String JavaDoc leftLabel = content.getLeftLabel(input);
1020            if (leftLabel != null)
1021                fLeftLabel.setText(TextProcessor.process(leftLabel));
1022        }
1023        if (fRightLabel != null) {
1024            Image rightImage = content.getRightImage(input);
1025            if (rightImage != null)
1026                fRightLabel.setImage(rightImage);
1027            String JavaDoc rightLabel = content.getRightLabel(input);
1028            if (rightLabel != null)
1029                fRightLabel.setText(TextProcessor.process(rightLabel));
1030        }
1031    }
1032        
1033    /*
1034     * Calculates the height of the header.
1035     */

1036    /* package */ int getHeaderHeight() {
1037        int headerHeight= fLeftLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y;
1038        headerHeight= Math.max(headerHeight, fDirectionLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y);
1039        return headerHeight;
1040    }
1041    
1042    //---- dirty state & saving state
1043

1044    /* (non-Javadoc)
1045     * @see org.eclipse.compare.IPropertyChangeNotifier#addPropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
1046     */

1047    public void addPropertyChangeListener(IPropertyChangeListener listener) {
1048        if (fListenerList == null)
1049            fListenerList= new ListenerList();
1050        fListenerList.add(listener);
1051    }
1052    
1053    /* (non-Javadoc)
1054     * @see org.eclipse.compare.IPropertyChangeNotifier#removePropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
1055     */

1056    public void removePropertyChangeListener(IPropertyChangeListener listener) {
1057        if (fListenerList != null) {
1058            fListenerList.remove(listener);
1059            if (fListenerList.isEmpty())
1060                fListenerList= null;
1061        }
1062    }
1063    
1064    private void fireDirtyState(boolean state) {
1065        Utilities.firePropertyChange(fListenerList, this, CompareEditorInput.DIRTY_STATE, null, new Boolean JavaDoc(state));
1066    }
1067    
1068    /**
1069     * Sets the dirty state of the left side of this viewer.
1070     * If the new value differs from the old
1071     * all registered listener are notified with
1072     * a <code>PropertyChangeEvent</code> with the
1073     * property name <code>CompareEditorInput.DIRTY_STATE</code>.
1074     *
1075     * @param dirty the state of the left side dirty flag
1076     */

1077    protected void setLeftDirty(boolean dirty) {
1078        if (isLeftDirty() != dirty) {
1079            fLeftSaveAction.setEnabled(dirty);
1080            // Only fire the event if the combined dirty state has changed
1081
if ((!isRightDirty() && !isLeftDirty()) || (!isRightDirty() && isLeftDirty()))
1082                fireDirtyState(dirty);
1083        }
1084    }
1085    
1086    /**
1087     * Sets the dirty state of the right side of this viewer.
1088     * If the new value differs from the old
1089     * all registered listener are notified with
1090     * a <code>PropertyChangeEvent</code> with the
1091     * property name <code>CompareEditorInput.DIRTY_STATE</code>.
1092     *
1093     * @param dirty the state of the right side dirty flag
1094     */

1095    protected void setRightDirty(boolean dirty) {
1096        if (isRightDirty() != dirty) {
1097            fRightSaveAction.setEnabled(dirty);
1098            // Only fire the event if the combined dirty state has changed
1099
if ((!isRightDirty() && !isLeftDirty()) || (isRightDirty() && !isLeftDirty()))
1100                fireDirtyState(dirty);
1101        }
1102    }
1103    
1104    /**
1105     * Method from the old internal <code>ISavable</code> interface
1106     * Save the viewers's content.
1107     * Note: this method is for internal use only. Clients should not call this method.
1108     * @param monitor a progress monitor
1109     * @throws CoreException
1110     * @deprecated use {@link IFlushable#flush(IProgressMonitor)}.
1111     */

1112    public void save(IProgressMonitor monitor) throws CoreException {
1113        flush(monitor);
1114    }
1115    
1116    /**
1117     * Flush any modifications made in the viewer into the compare input. This method
1118     * calls {@link #flushContent(Object, IProgressMonitor)} with the compare input
1119     * of the viewer as the first parameter.
1120     * @param monitor a progress monitor
1121     * @see org.eclipse.compare.contentmergeviewer.IFlushable#flush(org.eclipse.core.runtime.IProgressMonitor)
1122     * @since 3.3
1123     */

1124    public final void flush(IProgressMonitor monitor) {
1125        flushContent(getInput(), monitor);
1126    }
1127    
1128    /**
1129     * Flush the modified content back to input elements via the content provider.
1130     * The provided input may be the current input of the viewer or it may be
1131     * the previous input (i.e. this method may be called to flush modified content
1132     * during an input change).
1133     * @param input the compare input
1134     * @param monitor a progress monitor or <code>null</code> if the method
1135     * was call from a place where a progress monitor was not available.
1136     * @since 3.3
1137     */

1138    protected void flushContent(Object JavaDoc input, IProgressMonitor monitor) {
1139                
1140        // write back modified contents
1141
IMergeViewerContentProvider content= (IMergeViewerContentProvider) getContentProvider();
1142        
1143        boolean leftEmpty= content.getLeftContent(input) == null;
1144        boolean rightEmpty= content.getRightContent(input) == null;
1145
1146        if (getCompareConfiguration().isLeftEditable() && isLeftDirty()) {
1147            byte[] bytes= getContents(true);
1148            if (rightEmpty && bytes != null && bytes.length == 0)
1149                bytes= null;
1150            setLeftDirty(false);
1151            content.saveLeftContent(input, bytes);
1152        }
1153        
1154        if (getCompareConfiguration().isRightEditable() && isRightDirty()) {
1155            byte[] bytes= getContents(false);
1156            if (leftEmpty && bytes != null && bytes.length == 0)
1157                bytes= null;
1158            setRightDirty(false);
1159            content.saveRightContent(input, bytes);
1160        }
1161    }
1162
1163    /**
1164     * Return the dirty state of the right side of this viewer.
1165     * @return the dirty state of the right side of this viewer
1166     * @since 3.3
1167     */

1168    protected boolean isRightDirty() {
1169        return fRightSaveAction.isEnabled();
1170    }
1171
1172    /**
1173     * Return the dirty state of the left side of this viewer.
1174     * @return the dirty state of the left side of this viewer
1175     * @since 3.3
1176     */

1177    protected boolean isLeftDirty() {
1178        return fLeftSaveAction.isEnabled();
1179    }
1180
1181    /**
1182     * Handle a change to the given input reported from an {@link org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener}.
1183     * This class registers a listener with its input and reports any change events through
1184     * this method. By default, this method prompts for any unsaved changes and then refreshes
1185     * the viewer. Subclasses may override.
1186     * @since 3.3
1187     */

1188    protected void handleCompareInputChange() {
1189        // before setting the new input we have to save the old
1190
Object JavaDoc input = getInput();
1191        if (isLeftDirty() || isRightDirty()) {
1192            
1193            if (Utilities.RUNNING_TESTS) {
1194                if (Utilities.TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE) {
1195                    flushContent(input, null);
1196                }
1197            } else {
1198                // post alert
1199
Shell shell= fComposite.getShell();
1200                
1201                MessageDialog dialog= new MessageDialog(shell,
1202                    CompareMessages.ContentMergeViewer_resource_changed_title,
1203                    null, // accept the default window icon
1204
CompareMessages.ContentMergeViewer_resource_changed_description,
1205                    MessageDialog.QUESTION,
1206                    new String JavaDoc[] {
1207                        IDialogConstants.YES_LABEL, // 0
1208
IDialogConstants.NO_LABEL, // 1
1209
},
1210                    0); // default button index
1211

1212                switch (dialog.open()) { // open returns index of pressed button
1213
case 0:
1214                    flushContent(input, null);
1215                    break;
1216                case 1:
1217                    setLeftDirty(false);
1218                    setRightDirty(false);
1219                    break;
1220                }
1221            }
1222        }
1223        refresh();
1224    }
1225}
1226
Popular Tags