Skip to main content
summaryrefslogtreecommitdiffstats
blob: 1b2916d5c2cab084ecc54643907cadb5c8ed9cc4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
/*******************************************************************************
 * Copyright (c) 2017, 2018 EclipseSource Services GmbH 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:
 *     Philip Langer - initial API and implementation
 *******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.fallback;

import com.google.common.eventbus.Subscribe;

import java.util.EventObject;

import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareViewerSwitchingPane;
import org.eclipse.compare.ISharedDocumentAdapter;
import org.eclipse.compare.SharedDocumentAdapter;
import org.eclipse.compare.contentmergeviewer.ContentMergeViewer;
import org.eclipse.compare.contentmergeviewer.TextMergeViewer;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.SharedDocumentAdapterWrapper;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.command.ICompareCommandStack;
import org.eclipse.emf.compare.domain.ICompareEditingDomain;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.CompareInputAdapter;
import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.ForwardingCompareInput;
import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.rcp.ui.internal.configuration.ICompareEditingDomainChange;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.edit.tree.TreeNode;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.texteditor.IDocumentProvider;

/**
 * A highly specialized implementation of a text merge viewer.
 * 
 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
 */
@SuppressWarnings("restriction")
public class TextFallbackMergeViewer extends TextMergeViewer {

	static final String SHOW_PREVIEW = "SHOW_PREVIEW"; //$NON-NLS-1$

	/**
	 * We subvert the base class from seeing or notifying listeners. That's because editing the text makes the
	 * text dirty, but the EMF compare's editor should always reflect the dirty state of the command stack.
	 * 
	 * @see #addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
	 * @see #removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
	 * @see #updateDirtyState(ICompareCommandStack)
	 */
	@SuppressWarnings("rawtypes")
	private final ListenerList listenerList = new ListenerList();

	/**
	 * The original input passed to {@link #setInput(Object)}.
	 * 
	 * @see #getInput()
	 */
	private Object originalInput;

	/**
	 * The effective input computed during {@link #setInput(Object)} by
	 * {@link #getAdaptedCompareInput(CompareInputAdapter)}.
	 */
	private Object effectiveInput;

	/**
	 * The ancestor viewer in which to {@link #select(SourceViewer, EObject, Resource) select} objects.
	 * 
	 * @see #createSourceViewer(Composite, int)
	 */
	private SourceViewer ancesorViewer;

	/**
	 * The left viewer in which to {@link #select(SourceViewer, EObject, Resource) select} objects.
	 * 
	 * @see #createSourceViewer(Composite, int)
	 */
	private SourceViewer leftViewer;

	/**
	 * The right viewer in which to {@link #select(SourceViewer, EObject, Resource) select} objects.
	 * 
	 * @see #createSourceViewer(Composite, int)
	 */
	private SourceViewer rightViewer;

	/**
	 * Controls whether {@link #setContentProvider(IContentProvider) content provider changes} are respected
	 * or ignored.
	 * 
	 * @see #TextFallbackCompareViewerCreator(Composite, EMFCompareConfiguration)
	 * @see #handlePropertyChangeEvent(PropertyChangeEvent)
	 */
	private boolean ignoreContentProvideChanges = true;

	/**
	 * The item added by {@link #createToolItems(ToolBarManager)} and updated by {@link #updateToolItems()}.
	 * It's used to provide the ability to preview the contents of a resource as if it were saved.
	 */
	private ActionContributionItem previewItem;

	/**
	 * The command stack using during {@link #updateDirtyState(ICompareCommandStack) dirty state updates}.
	 */
	private ICompareCommandStack commandStackForNotification;

	/**
	 * A command stack listener that listens to the
	 * {@link #editingDomainChange(ICompareEditingDomain, ICompareEditingDomain) editing domain's command
	 * stack}. It {@link #updateDirtyState(ICompareCommandStack) updates the dirty state} and
	 * {@link #updateTitleImage() updates the title image}.
	 */
	private final CommandStackListener commandStackListener = new CommandStackListener() {
		public void commandStackChanged(EventObject event) {
			Object commandStack = event.getSource();
			if (commandStack instanceof ICompareCommandStack) {
				ICompareCommandStack compareCommandStack = (ICompareCommandStack)commandStack;
				updateDirtyState(compareCommandStack);
				updateTitleImage();
			}
			setInput(getOriginalInput());
		}
	};

	/**
	 * Creates an instance under the given parent using the given configuration.
	 * 
	 * @param parent
	 *            the parent composite under which to create this viewer.
	 * @param configuration
	 *            the EMF compare configuration used by this viewer.
	 */
	public TextFallbackMergeViewer(Composite parent, EMFCompareConfiguration configuration) {
		super(parent, configuration);

		// Register with the event bus.
		configuration.getEventBus().register(this);

		// Hook up the command stack listener to the editing domain's command stack.
		editingDomainChange(null, getCompareConfiguration().getEditingDomain());

		// Set our content provider, ensuring that it's not ignored during the update.
		ignoreContentProvideChanges = false;
		setContentProvider(new TextFallbackMergeViewerContentProvider(this));
		ignoreContentProvideChanges = true;
	}

	/**
	 * Listens to editing domain changes on the {@link EMFCompareConfiguration#getEventBus() event bus}.
	 * 
	 * @param event
	 *            the editing domain change event.
	 */
	@Subscribe
	public void handleEditingDomainChange(ICompareEditingDomainChange event) {
		editingDomainChange(event.getOldValue(), event.getNewValue());
	}

	/**
	 * Manages the {{@link #commandStackListener command stack listener} by removing it from the old editing
	 * domain's {@link ICompareEditingDomain#getCommandStack() command stack} and adding it to the new editing
	 * domain's command stack.
	 * 
	 * @param oldValue
	 *            the previous editing domain.
	 * @param newValue
	 *            the new editing domain.
	 */
	private void editingDomainChange(ICompareEditingDomain oldValue, ICompareEditingDomain newValue) {
		if (newValue != oldValue) {
			if (oldValue != null) {
				oldValue.getCommandStack().removeCommandStackListener(commandStackListener);
			}
			if (newValue != null) {
				newValue.getCommandStack().addCommandStackListener(commandStackListener);
				updateDirtyState(newValue.getCommandStack());
			}
		}
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation subverts calls to {@code super} so that nothing can actually listen to underlying
	 * state changes to the dirty state of this viewer's source viewers. It manages its own
	 * {@link #listenerList listeners} and {@link #updateDirtyState(ICompareCommandStack) informs listeners of
	 * the dirty state} based on changes to {@link #commandStackListener command stack state}.
	 * </p>
	 * 
	 * @see ContentMergeViewer#removePropertyChangeListener(IPropertyChangeListener)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void addPropertyChangeListener(IPropertyChangeListener listener) {
		listenerList.add(listener);
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation subverts calls to {@code super} so that nothing can actually listen to underlying
	 * state changes to the dirty state of this viewer's source viewers. It manages its own
	 * {@link #listenerList listeners}.
	 * </p>
	 * 
	 * @see ContentMergeViewer#removePropertyChangeListener(IPropertyChangeListener)
	 */
	@Override
	public void removePropertyChangeListener(IPropertyChangeListener listener) {
		listenerList.remove(listener);
	}

	/**
	 * {@link Utilities#firePropertyChange(ListenerList, Object, String, Object, Object) Fires} a
	 * {@link CompareEditorInput#DIRTY_STATE dirty state} event to the {@link #listenerList listeners} of this
	 * viewer. It ensures that calls to the {link {@link #isLeftDirty()} and {@link #isRightDirty()} return
	 * the state of the {@link #commandStackForNotification command stack}.
	 * 
	 * @param commandStack
	 *            the command stack whose state should be used to update dirtiness.
	 */
	@SuppressWarnings("unchecked")
	private void updateDirtyState(ICompareCommandStack commandStack) {
		// Don't change the dirty state of the part itself, because that's managed by actual changes to
		// the source viewers.
		this.commandStackForNotification = commandStack;
		Utilities.firePropertyChange(listenerList, this, CompareEditorInput.DIRTY_STATE, null,
				Boolean.valueOf(commandStack.isLeftSaveNeeded() || commandStack.isRightSaveNeeded()));
		this.commandStackForNotification = null;
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation returns the {@link ICompareCommandStack#isLeftSaveNeeded() state} of the
	 * {@link #commandStackForNotification command stack} when the command stack is
	 * {@link #updateDirtyState(ICompareCommandStack) updating the dirty state}.
	 * </p>
	 * 
	 * @see ContentMergeViewer#isLeftDirty()
	 */
	@Override
	protected boolean isLeftDirty() {
		if (commandStackForNotification == null) {
			return super.isLeftDirty();
		} else {
			return commandStackForNotification.isLeftSaveNeeded();
		}
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation {@link #updateHeader() updates the header} because the label
	 * {@link TextFallbackCompareInputLabelProvider#getLabel(Resource, IStorage, boolean) includes a dirty
	 * state indication}.
	 * </p>
	 * 
	 * @see ContentMergeViewer#setLeftDirty(boolean)
	 */
	@Override
	protected void setLeftDirty(boolean dirty) {
		super.setLeftDirty(dirty);
		updateHeader();
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation returns the {@link ICompareCommandStack#isRightSaveNeeded() state} of the
	 * {@link #commandStackForNotification command stack} when the command stack is
	 * {@link #updateDirtyState(ICompareCommandStack) updating the dirty state}.
	 * </p>
	 * 
	 * @see ContentMergeViewer#isRightDirty()
	 */
	@Override
	protected boolean isRightDirty() {
		if (commandStackForNotification == null) {
			return super.isRightDirty();
		} else {
			return commandStackForNotification.isRightSaveNeeded();
		}
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation {@link #updateHeader() updates the header} because the label
	 * {@link TextFallbackCompareInputLabelProvider#getLabel(Resource, IStorage, boolean) includes a dirty
	 * state indication}.
	 * </p>
	 * 
	 * @see ContentMergeViewer#setRightDirty(boolean)
	 */
	@Override
	protected void setRightDirty(boolean dirty) {
		super.setRightDirty(dirty);
		updateHeader();
	}

	/**
	 * Updates the title image on the {@link CompareViewerSwitchingPane compare viewer switching pane}. This
	 * is needed when the {@link #commandStackListener command stack changes state} and when
	 * {@link #handlePropertyChangeEvent(PropertyChangeEvent) handling mirror direction changes}. The image
	 * often includes a directional indicator or a {@link Diff#getState() difference resolution state} that
	 * changes.
	 */
	private void updateTitleImage() {
		if (getInput() instanceof ICompareInput) {
			Composite parent = getControl().getParent();
			if (parent instanceof CompareViewerSwitchingPane) {
				CompareViewerSwitchingPane switchingPane = (CompareViewerSwitchingPane)parent;
				switchingPane.setImage(((ICompareInput)getInput()).getImage());
			}
		}

	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation also creates a {@link #previewItem preview action} that's useful for showing the
	 * saved contents of resource in their current state of modification.
	 * </p>
	 * 
	 * @see TextMergeViewer#createToolItems(ToolBarManager)
	 */
	@Override
	protected void createToolItems(ToolBarManager tbm) {
		super.createToolItems(tbm);

		Action previewAction = new Action() {
			{
				setChecked(getCompareConfiguration().getBooleanProperty(SHOW_PREVIEW, true));
				setImageDescriptor(
						EMFCompareIDEUIPlugin.getImageDescriptor("icons/full/toolb16/show_preview.gif")); //$NON-NLS-1$
				updateToolTipText();
			}

			@Override
			public void run() {
				getCompareConfiguration().setProperty(SHOW_PREVIEW, Boolean.valueOf(isChecked()));
				setInput(getOriginalInput());
				updateToolTipText();
			}

			private void updateToolTipText() {
				if (isChecked()) {
					setToolTipText(
							EMFCompareIDEUIMessages.getString("TextFallbackCompareViewer.hidePreviewLabel")); //$NON-NLS-1$
				} else {
					setToolTipText(
							EMFCompareIDEUIMessages.getString("TextFallbackCompareViewer.showPreviewLabel")); //$NON-NLS-1$
				}
			}
		};

		tbm.appendToGroup("modes", previewAction); //$NON-NLS-1$
		previewItem = new ActionContributionItem(previewAction);
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation also updates the {@link #previewItem preview action}.
	 * </p>
	 * 
	 * @see TextMergeViewer#updateToolItems()
	 */
	@Override
	protected void updateToolItems() {
		previewItem.setVisible(getEffectiveInput() instanceof TextFallbackCompareInput);
		super.updateToolItems();
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation assumes the configuration must be an {@link EMFCompareConfiguration}, returning it
	 * as such.
	 * </p>
	 * 
	 * @see ContentMergeViewer#getCompareConfiguration()
	 */
	@Override
	protected EMFCompareConfiguration getCompareConfiguration() {
		return (EMFCompareConfiguration)super.getCompareConfiguration();
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This specialized implementation {@link #ignoreContentProvideChanges ignores} content provider changes
	 * except when explicitly set in the
	 * {@link #TextFallbackCompareViewerCreator(Composite, EMFCompareConfiguration) constructor} and when this
	 * implementation is {@link #handlePropertyChangeEvent(PropertyChangeEvent) handling mirror changes}.
	 * </p>
	 * 
	 * @see TextMergeViewer#setContentProvider(IContentProvider)
	 */
	@Override
	public void setContentProvider(IContentProvider contentProvider) {
		if (!ignoreContentProvideChanges) {
			super.setContentProvider(contentProvider);
		}
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation is specialized to {@link #getAdaptedCompareInput(CompareInputAdapter) adapt} the
	 * input and to {@link #select(SourceViewer, EObject, Resource) select} the objects of the input.
	 * </p>
	 * 
	 * @see TextMergeViewer#setInput(Object)
	 */
	@Override
	public void setInput(Object input) {
		Control control = getControl();
		try {
			// Disable painting to reduce flicker during the update process.
			control.setRedraw(false);

			// We set this to null while calling the super methods.
			// This ensures that getInput() will return the actual old input during the input switching
			// process until the super call has actually set the field and is returning the input we've
			// just set.
			// If we don't do this, any edits will not be saved when the user is prompted whether to save
			// the changes or discard them because that relies on the actual old input.
			setOriginalInput(null);
			if (input instanceof CompareInputAdapter) {
				setEffectiveInput(getAdaptedCompareInput((CompareInputAdapter)input));
			} else if (input instanceof ForwardingCompareInput) {
				setEffectiveInput(((ForwardingCompareInput)input).delegate());
			} else {
				setEffectiveInput(input);
			}
			super.setInput(getEffectiveInput());

			// From this point forward, getInput() will return the original input.
			// This ensures that when we switch to a different view that disposes this view,
			// the input for that new view is this original input.
			setOriginalInput(input);

			// If we have a text compare input...
			if (getEffectiveInput() instanceof TextFallbackCompareInput) {
				TextFallbackCompareInput textCompareInput = (TextFallbackCompareInput)getEffectiveInput();
				TextFallbackCompareInputData textInputData = textCompareInput.getTextInputData();
				// Select the objects on each of the sides.
				select(leftViewer, textInputData.getLeft(), textInputData.getLeftResource());
				select(rightViewer, textInputData.getRight(), textInputData.getRightResource());
				select(ancesorViewer, textInputData.getOrigin(), textInputData.getOriginResource());
			}
		} finally {
			control.setRedraw(true);
		}
	}

	/**
	 * Adapts the real input passed to {@link #setInput(Object)} to an input more appropriate for this viewer.
	 * 
	 * @param input
	 *            a compare input adapter.
	 * @return the input adapted to the appropriate input to use for this viewer.
	 * @see TextFallbackCompareInputData
	 * @see TextFallbackCompareInput
	 */
	private ICompareInput getAdaptedCompareInput(CompareInputAdapter input) {
		ICompareInput adaptedCompareInput;
		Notifier target = input.getTarget();
		// If this is an adapter for a tree node....
		if (target instanceof TreeNode) {
			// Get the tree node's data and determine its comparison; we generally expect that to never be
			// null.
			TreeNode treeNode = (TreeNode)target;
			EObject data = treeNode.getData();
			Comparison comparison = ComparisonUtil.getComparison(data);
			if (comparison != null) {
				// Adapt the input and if it's a forwarding compare input, unwrap the delegate.
				ICompareInput compareInput = (ICompareInput)EcoreUtil.getAdapter(comparison.eAdapters(),
						ICompareInput.class);
				if (compareInput instanceof ForwardingCompareInput) {
					adaptedCompareInput = ((ForwardingCompareInput)compareInput).delegate();
				} else {
					adaptedCompareInput = compareInput;
				}
				// Compute the most appropriate text input data from the tree node's data.
				// Only use it as the adapted compare input if it succeeds to compute at least one typed
				// element for ones of the sides.
				TextFallbackCompareInputData textInputData = new TextFallbackCompareInputData(data);
				if (textInputData.hasTypedElement()) {
					adaptedCompareInput = new TextFallbackCompareInput(adaptedCompareInput.getKind(),
							textInputData, getCompareConfiguration().getBooleanProperty(SHOW_PREVIEW, true));
				}
			} else {
				adaptedCompareInput = null;
				EMFCompareIDEUIPlugin.getDefault().log(IStatus.ERROR,
						"Cannot find a comparison from input " + input); //$NON-NLS-1$
			}
		} else {
			adaptedCompareInput = null;
		}
		return adaptedCompareInput;
	}

	/**
	 * Selects the the source viewer's text line closest to the given object of the given resource. If that
	 * line contains a difference, that difference is also selected.
	 * 
	 * @param sourceViewer
	 *            the source viewer on which to operate.
	 * @param eObject
	 *            the object to be selected.
	 * @param resource
	 *            the resource containing the object.
	 */
	private void select(SourceViewer sourceViewer, EObject eObject, Resource resource) {
		IDocument document = sourceViewer.getDocument();
		int offset = getOffset(eObject, resource, document.get());
		if (offset != -1) {
			sourceViewer.setSelectedRange(offset, 0);
			sourceViewer.setSelection(sourceViewer.getSelection(), true);

			// The viewer listens for key events for keyboard navigation updates to the selected
			// difference and this approach updates that selection based on the current selected range.
			Event event = new Event();
			StyledText textWidget = sourceViewer.getTextWidget();
			textWidget.notifyListeners(SWT.KeyDown, event);
			textWidget.notifyListeners(SWT.KeyUp, event);
		}
	}

	/**
	 * Returns the offset of the start of the line containing then object of the given resource serialized for
	 * the given text.
	 * 
	 * @param eObject
	 *            the object to be selected.
	 * @param resource
	 *            the resource containing the object.
	 * @param text
	 *            the text of the serialized resource.
	 * @return the offset of the start of the line containing then object of the given resource serialized for
	 *         the given text, or {@code -1} if the object can't be located.
	 */
	private int getOffset(EObject eObject, Resource resource, String text) {
		int offset = -1;
		if (resource instanceof XMLResource && eObject != null) {
			// This only works if there is an object and the resource is an XML Resource that has an
			// extrinsic ID for the object.
			XMLResource xmlResource = (XMLResource)resource;
			String id = xmlResource.getID(eObject);
			if (id != null) {
				// Look for the expected form of the ID in the serialization.
				offset = text.indexOf("xmi:id=\"" + id + '"'); //$NON-NLS-1$
				if (offset != -1) {
					// If we find it, iterate back to the start of the line.
					while (offset > 0) {
						char c = text.charAt(offset - 1);
						if (c == '\n' || c == '\r') {
							break;
						}
						--offset;
					}
				}
			}
		}
		return offset;
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation simply delegates to {@code super} but it is used to record the
	 * {@link #ancesorViewer}, {@link #leftViewer}, and {@link #rightViewer} which are needed in
	 * {@link #setInput(Object)} to {@link #select(SourceViewer, EObject, Resource) select} objects.
	 * </p>
	 * 
	 * @see TextMergeViewer#createSourceViewer(Composite, int)
	 */
	@Override
	protected SourceViewer createSourceViewer(Composite parent, int textOrientation) {
		SourceViewer sourceViewer = super.createSourceViewer(parent, textOrientation);
		if (ancesorViewer == null) {
			ancesorViewer = sourceViewer;
		} else if (leftViewer == null) {
			leftViewer = sourceViewer;
		} else {
			rightViewer = sourceViewer;
		}
		return sourceViewer;
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This class' specialized {@link #setInput(Object) setInput} method transforms the real input, so it's
	 * important that we return the {@link #originalInput} when that isn't {@code null}.
	 * </p>
	 * 
	 * @see #setInput(Object)
	 * @see org.eclipse.jface.viewers.ContentViewer#getInput()
	 */
	@Override
	public Object getInput() {
		if (getOriginalInput() == null) {
			return super.getInput();
		} else {
			return getOriginalInput();
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see org.eclipse.compare.contentmergeviewer.TextMergeViewer#handleDispose(org.eclipse.swt.events.DisposeEvent)
	 */
	@Override
	protected void handleDispose(DisposeEvent event) {
		super.handleDispose(event);

		// Disconnect from the editing domain.
		EMFCompareConfiguration configuration = getCompareConfiguration();
		editingDomainChange(configuration.getEditingDomain(), null);

		// Disconnect from the event bus.
		configuration.getEventBus().unregister(this);

		// Clean up the inputs.
		setOriginalInput(null);
		setEffectiveInput(null);
	}

	/**
	 * Opens a connection to the document provider and returns a runnable that will disconnect it.
	 * 
	 * @param element
	 *            the left or right element.
	 * @return a runnable to disconnect the connection, or null, if no connection was established.
	 */
	private Runnable connectDocumentProvider(Object element) {
		if (element != null) {
			final ISharedDocumentAdapter sharedDocumentationAdapter = SharedDocumentAdapterWrapper
					.getAdapter(element);
			if (sharedDocumentationAdapter != null) {
				final IEditorInput documentKey = sharedDocumentationAdapter.getDocumentKey(element);
				if (documentKey != null) {
					final IDocumentProvider documentProvider = SharedDocumentAdapter
							.getDocumentProvider(documentKey);
					if (documentProvider != null) {
						try {
							sharedDocumentationAdapter.connect(documentProvider, documentKey);
							return new Runnable() {
								public void run() {
									sharedDocumentationAdapter.disconnect(documentProvider, documentKey);
								}
							};
						} catch (CoreException e) {
							EMFCompareIDEUIPlugin.getDefault().log(e);
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#getTitle()
	 */
	@Override
	public String getTitle() {
		return EMFCompareIDEUIMessages.getString("TextFallbackCompareViewer.title"); //$NON-NLS-1$
	}

	public Object getOriginalInput() {
		return originalInput;
	}

	private void setOriginalInput(Object originalInput) {
		this.originalInput = originalInput;
	}

	public Object getEffectiveInput() {
		return effectiveInput;
	}

	private void setEffectiveInput(Object effectiveInput) {
		this.effectiveInput = effectiveInput;
	}
}

Back to the top