1 /*******************************************************************************
2 * Copyright (c) 2013 Obeo.
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
9 * Obeo - initial API and implementation
10 *******************************************************************************/
11 package org
.eclipse
.emf
.compare
.ide
.ui
.internal
.structuremergeviewer
;
13 import com
.google
.common
.collect
.HashMultimap
;
14 import com
.google
.common
.collect
.Iterables
;
15 import com
.google
.common
.collect
.Lists
;
16 import com
.google
.common
.collect
.Maps
;
17 import com
.google
.common
.collect
.Multimap
;
18 import com
.google
.common
.collect
.Sets
;
20 import java
.util
.Collection
;
21 import java
.util
.LinkedList
;
22 import java
.util
.List
;
26 import org
.eclipse
.compare
.CompareConfiguration
;
27 import org
.eclipse
.core
.runtime
.Assert
;
28 import org
.eclipse
.emf
.common
.notify
.Adapter
;
29 import org
.eclipse
.emf
.common
.notify
.Notifier
;
30 import org
.eclipse
.emf
.compare
.Diff
;
31 import org
.eclipse
.emf
.compare
.internal
.utils
.DiffUtil
;
32 import org
.eclipse
.emf
.compare
.rcp
.ui
.internal
.EMFCompareConstants
;
33 import org
.eclipse
.emf
.compare
.rcp
.ui
.internal
.structuremergeviewer
.filters
.IDifferenceFilter
;
34 import org
.eclipse
.emf
.ecore
.EObject
;
35 import org
.eclipse
.emf
.edit
.tree
.TreeNode
;
36 import org
.eclipse
.jface
.resource
.JFaceResources
;
37 import org
.eclipse
.jface
.viewers
.ISelection
;
38 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
39 import org
.eclipse
.jface
.viewers
.SelectionChangedEvent
;
40 import org
.eclipse
.jface
.viewers
.TreePath
;
41 import org
.eclipse
.jface
.viewers
.TreeViewer
;
42 import org
.eclipse
.swt
.SWT
;
43 import org
.eclipse
.swt
.events
.MouseEvent
;
44 import org
.eclipse
.swt
.events
.MouseListener
;
45 import org
.eclipse
.swt
.events
.MouseMoveListener
;
46 import org
.eclipse
.swt
.events
.MouseTrackListener
;
47 import org
.eclipse
.swt
.events
.PaintEvent
;
48 import org
.eclipse
.swt
.events
.PaintListener
;
49 import org
.eclipse
.swt
.graphics
.Color
;
50 import org
.eclipse
.swt
.graphics
.Cursor
;
51 import org
.eclipse
.swt
.graphics
.GC
;
52 import org
.eclipse
.swt
.graphics
.Rectangle
;
53 import org
.eclipse
.swt
.widgets
.Canvas
;
54 import org
.eclipse
.swt
.widgets
.Composite
;
55 import org
.eclipse
.swt
.widgets
.ScrollBar
;
56 import org
.eclipse
.swt
.widgets
.Tree
;
57 import org
.eclipse
.swt
.widgets
.TreeItem
;
60 * A specific canvas that must be presented next to a TreeViewer. It shows consequences of a Diff (required
61 * and unmergeable differences), as in the TreeViewer, but in a compact format (small colored rectangles) and
62 * with links to respectives Treeitems.
64 * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
66 public class EMFCompareDiffTreeRuler
extends Canvas
{
68 /** The vertical offset for an annotation. */
69 private static final int Y_OFFSET
= 6;
71 /** The height of an annotation. */
72 private static final int ANNOTATION_HEIGHT
= 5;
74 /** The TreeViewer associated with this Treeruler. */
75 private final TreeViewer fTreeViewer
;
77 /** The color a required diff. */
78 private final Color requiredDiffFillColor
;
80 /** The color of an unmergeable diff. **/
81 private final Color unmergeableDiffFillColor
;
83 /** The border color a required diff. */
84 private final Color requiredDiffBorderColor
;
86 /** The border color an unmergeable diff. */
87 private final Color unmergeableDiffBorderColor
;
89 /** The width of this tree ruler. */
90 private final int fWidth
;
92 /** The list of required Diff that need to be shown in the TreeRuler. */
93 private Set
<Diff
> requires
;
95 /** The list of unmergeables Diff that need to be shown in the TreeRuler. */
96 private Set
<Diff
> unmergeables
;
98 /** A map that links a diff with tree items. */
99 private Multimap
<Diff
, TreeItem
> diffItems
;
101 /** A map that links a rectangle with a tree item. */
102 private Map
<Rectangle
, TreeItem
> annotationsData
;
104 /** The paint listener. */
105 private PaintListener paintListener
;
107 /** The mouse click listener. */
108 private MouseListener mouseClickListener
;
110 /** The mouse move listener. */
111 private MouseMoveListener mouseMoveListener
;
113 /** The mouse track listener. */
114 private MouseTrackListener mouseTrackListener
;
116 /** The last cursor used. */
117 private Cursor lastCursor
;
119 /** The configuration for this control. */
120 private CompareConfiguration fConfiguration
;
122 /** The selected diff in the Treeviewer associated with this Treeruler. */
123 private Diff selectedDiff
;
129 * the control's parent.
131 * the style of the control to construct.
133 * the control's width.
135 * the TreeViewer associated with this control.
137 * the configuration for this control.
139 EMFCompareDiffTreeRuler(Composite parent
, int style
, int width
, TreeViewer treeViewer
,
140 CompareConfiguration config
) {
141 super(parent
, style
);
143 fTreeViewer
= treeViewer
;
144 fConfiguration
= config
;
146 requiredDiffFillColor
= JFaceResources
.getColorRegistry().get(
147 EMFCompareDiffTreeViewer
.REQUIRED_DIFF_COLOR
);
148 requiredDiffBorderColor
= JFaceResources
.getColorRegistry().get(
149 EMFCompareDiffTreeViewer
.REQUIRED_DIFF_BORDER_COLOR
);
150 unmergeableDiffFillColor
= JFaceResources
.getColorRegistry().get(
151 EMFCompareDiffTreeViewer
.UNMERGEABLE_DIFF_COLOR
);
152 unmergeableDiffBorderColor
= JFaceResources
.getColorRegistry().get(
153 EMFCompareDiffTreeViewer
.UNMERGEABLE_DIFF_BORDER_COLOR
);
155 requires
= Sets
.newHashSet();
156 unmergeables
= Sets
.newHashSet();
157 diffItems
= HashMultimap
.create();
158 annotationsData
= Maps
.newHashMap();
160 paintListener
= new PaintListener() {
161 public void paintControl(PaintEvent e
) {
165 addPaintListener(paintListener
);
167 mouseClickListener
= new MouseListener() {
169 public void mouseUp(MouseEvent e
) {
170 handleMouseClickEvent(e
);
173 public void mouseDown(MouseEvent e
) {
177 public void mouseDoubleClick(MouseEvent e
) {
181 addMouseListener(mouseClickListener
);
183 mouseMoveListener
= new MouseMoveListener() {
185 public void mouseMove(MouseEvent e
) {
186 handleMouveMoveEvent(e
);
189 addMouseMoveListener(mouseMoveListener
);
191 mouseTrackListener
= new MouseTrackListener() {
193 public void mouseHover(MouseEvent e
) {
194 handleMouseHoverEvent(e
);
197 public void mouseExit(MouseEvent e
) {
201 public void mouseEnter(MouseEvent e
) {
205 addMouseTrackListener(mouseTrackListener
);
209 * Compute consequences (required and unmergeable differences) when selection changed occurs.
212 * the SelectionChangedEvent event.
214 public void selectionChanged(SelectionChangedEvent event
) {
216 ISelection selection
= event
.getSelection();
217 if (selection
instanceof IStructuredSelection
) {
218 Object element
= ((IStructuredSelection
)selection
).getFirstElement();
219 if (element
instanceof Adapter
) {
220 Object target
= ((Adapter
)element
).getTarget();
221 if (target
instanceof TreeNode
) {
222 EObject data
= ((TreeNode
)target
).getData();
223 if (data
instanceof Diff
) {
224 selectedDiff
= (Diff
)data
;
225 computeConsequences();
233 * Compute consequences (required and unmergeable differences).
235 public void computeConsequences() {
237 if (selectedDiff
!= null) {
238 Boolean leftToRight
= (Boolean
)fConfiguration
.getProperty(EMFCompareConstants
.MERGE_WAY
);
240 if (leftToRight
== null || leftToRight
.booleanValue()) {
243 boolean leftEditable
= fConfiguration
.isLeftEditable();
244 boolean rightEditable
= fConfiguration
.isRightEditable();
245 if (ltr
|| (!ltr
&& (rightEditable
&& !leftEditable
))) {
246 requires
= DiffUtil
.getRequires(selectedDiff
, true, selectedDiff
.getSource());
247 unmergeables
= DiffUtil
.getUnmergeables(selectedDiff
, true);
249 requires
= DiffUtil
.getRequires(selectedDiff
, false, selectedDiff
.getSource());
250 unmergeables
= DiffUtil
.getUnmergeables(selectedDiff
, false);
252 associateTreeItems(Lists
.newLinkedList(Iterables
.concat(requires
, unmergeables
)));
257 * Maps tree items with the given list of diffs.
260 * the given list of diffs.
262 private void associateTreeItems(List
<Diff
> diffs
) {
263 Tree tree
= fTreeViewer
.getTree();
264 for (TreeItem item
: tree
.getItems()) {
265 associateTreeItem(item
, diffs
);
270 * Maps, if necessary, the given tree item and all his children with the given list of diffs.
273 * the given tree item.
275 * the given list of diffs.
277 private void associateTreeItem(TreeItem item
, List
<Diff
> diffs
) {
278 Object data
= item
.getData();
279 if (data
instanceof Adapter
) {
280 Notifier target
= ((Adapter
)data
).getTarget();
281 if (target
instanceof TreeNode
) {
282 EObject treeNodeData
= ((TreeNode
)target
).getData();
283 if (diffs
.contains(treeNodeData
)) {
284 diffItems
.put((Diff
)treeNodeData
, item
);
288 for (TreeItem child
: item
.getItems()) {
289 associateTreeItem(child
, diffs
);
296 private void clearAllData() {
298 unmergeables
.clear();
300 annotationsData
.clear();
304 * Handles the dispose event on this control.
306 public void handleDispose() {
307 removeMouseTrackListener(mouseTrackListener
);
308 removeMouseMoveListener(mouseMoveListener
);
309 removeMouseListener(mouseClickListener
);
310 removePaintListener(paintListener
);
314 * Handles the paint event.
319 private void handlePaintEvent(PaintEvent e
) {
320 annotationsData
.clear();
321 Collection
<IDifferenceFilter
> filters
= (Collection
<IDifferenceFilter
>)fConfiguration
322 .getProperty(EMFCompareConstants
.SELECTED_FILTERS
);
323 Collection
<?
extends Diff
> filteredRequires
= filteredDiffs(requires
, filters
);
324 Collection
<?
extends Diff
> filteredUnmergeables
= filteredDiffs(unmergeables
, filters
);
325 for (Diff diff
: filteredRequires
) {
326 for (TreeItem item
: diffItems
.get(diff
)) {
327 createAnnotation(e
, diff
, item
, requiredDiffFillColor
, requiredDiffBorderColor
);
330 for (Diff diff
: filteredUnmergeables
) {
331 for (TreeItem item
: diffItems
.get(diff
)) {
332 createAnnotation(e
, diff
, item
, unmergeableDiffFillColor
, unmergeableDiffBorderColor
);
338 * Handles the mouse click event.
341 * the mouse click event.
343 private void handleMouseClickEvent(MouseEvent e
) {
344 for (Rectangle rect
: annotationsData
.keySet()) {
345 if (e
.y
>= rect
.y
&& e
.y
<= rect
.y
+ ANNOTATION_HEIGHT
) {
346 TreeItem item
= annotationsData
.get(rect
);
347 TreePath treePath
= getTreePathFromItem(item
);
348 fTreeViewer
.expandToLevel(treePath
, 0);
349 fTreeViewer
.reveal(treePath
);
350 if (isVerticalScrollBarEnabled()) {
351 TreeItem previousItem
= getPreviousItem(item
, 2);
352 fTreeViewer
.getTree().setTopItem(previousItem
);
361 * Handles the mouse move event.
364 * the mouse move event.
366 private void handleMouveMoveEvent(MouseEvent e
) {
367 Cursor cursor
= null;
368 for (Rectangle rect
: annotationsData
.keySet()) {
369 if (e
.y
>= rect
.y
&& e
.y
<= rect
.y
+ ANNOTATION_HEIGHT
) {
370 cursor
= e
.display
.getSystemCursor(SWT
.CURSOR_HAND
);
374 if (cursor
!= lastCursor
) {
381 * Handles the mouse hover event.
384 * the mouve hover event.
386 private void handleMouseHoverEvent(MouseEvent e
) {
387 String overview
= ""; //$NON-NLS-1$
388 for (Rectangle rect
: annotationsData
.keySet()) {
389 if (e
.y
>= rect
.y
&& e
.y
<= rect
.y
+ ANNOTATION_HEIGHT
) {
390 TreeItem item
= annotationsData
.get(rect
);
391 overview
= item
.getText();
395 setToolTipText(overview
);
399 * Create an annotation in the tree ruler.
404 * the Diff for which we want to create the annotation.
406 * the tree item associated with the diff.
408 * the annotation's fill color.
410 * the annotation's border color.
412 private void createAnnotation(PaintEvent e
, Diff diff
, TreeItem treeItem
, Color fill
, Color border
) {
413 TreeItem item
= getDeepestVisibleTreeItem(treeItem
, treeItem
);
415 int y
= item
.getBounds().y
;
416 int yRuler
= getSize().y
;
417 if (isVerticalScrollBarEnabled()) {
418 int yMin
= Math
.abs(item
.getParent().getItems()[0].getBounds().y
);
419 int yMax
= getLastVisibleItem().getBounds().y
;
420 int realYMax
= yMax
+ yMin
;
421 y
= (y
+ yMin
) * yRuler
/ realYMax
;
422 if (y
+ Y_OFFSET
+ ANNOTATION_HEIGHT
> yRuler
) {
423 y
= yRuler
- Y_OFFSET
- ANNOTATION_HEIGHT
;
426 Rectangle rect
= drawAnnotation(e
.gc
, 2, y
+ Y_OFFSET
, fWidth
- 5, ANNOTATION_HEIGHT
, fill
,
428 annotationsData
.put(rect
, treeItem
);
433 * Returns the full tree path of the given tree item.
436 * the given tree item.
437 * @return the full tree path of the given tree item.
439 private TreePath
getTreePathFromItem(TreeItem item
) {
440 LinkedList
<Object
> segments
= Lists
.newLinkedList();
441 TreeItem parent
= item
;
442 while (parent
!= null) {
443 Object segment
= parent
.getData();
444 Assert
.isNotNull(segment
);
445 segments
.addFirst(segment
);
446 parent
= parent
.getParentItem();
448 return new TreePath(segments
.toArray());
452 * Checks if the vertical scroll bar of the tree viewer associated with this tree ruler is activated and
455 * @return true if the vertical scroll bar is activated and enabled, false otherwise.
457 private boolean isVerticalScrollBarEnabled() {
458 ScrollBar verticalBar
= fTreeViewer
.getTree().getVerticalBar();
459 if (verticalBar
!= null) {
460 return verticalBar
.isVisible() && verticalBar
.isEnabled();
466 * Draw an annotation (a Rectangle) on this tree ruler.
471 * the x coordinate of the origin of the annotation.
473 * the y coordinate of the origin of the annotation.
475 * the width of the annotation.
477 * the height of the annotation.
479 * the annotation's fill color.
481 * the annotation's border color.
482 * @return the annotation (a Rectangle).
484 private Rectangle
drawAnnotation(GC gc
, int x
, int y
, int w
, int h
, Color fill
, Color border
) {
485 Rectangle rect
= new Rectangle(x
, y
, w
, h
);
486 gc
.setBackground(fill
);
487 gc
.fillRectangle(rect
);
489 gc
.setForeground(border
);
490 gc
.drawRectangle(x
, y
, w
, h
);
495 * Returns, for the given tree item, the deepest visible {@link TreeItem} in the Treeviewer associated
496 * with this TreeRuler.
499 * the given tree item.
500 * @param deepestVisibleItem
501 * the deepest visible tree item (a parent or the item itself) of the given item.
502 * @return the deepest visible tree item (a parent or the item itself).
504 private TreeItem
getDeepestVisibleTreeItem(final TreeItem currentItem
, final TreeItem deepestVisibleItem
) {
505 TreeItem item
= null;
506 if (!currentItem
.isDisposed()) {
507 TreeItem parent
= currentItem
.getParentItem();
508 if (parent
== null) {
509 item
= deepestVisibleItem
;
510 } else if (parent
.getExpanded()) {
511 item
= getDeepestVisibleTreeItem(parent
, deepestVisibleItem
);
513 item
= getDeepestVisibleTreeItem(parent
, parent
);
520 * Get the previous item of the given {@link TreeItem}.
523 * the given {@link TreeItem}.
525 * the index of the previous item.
526 * @return the previous item of the given {@link TreeItem}.
528 private TreeItem
getPreviousItem(TreeItem treeItem
, int index
) {
529 TreeItem previousItem
= treeItem
;
531 TreeItem parentItem
= treeItem
.getParentItem();
532 if (parentItem
!= null) {
533 int treeItemIndex
= 0;
534 for (TreeItem siblingItem
: parentItem
.getItems()) {
535 if (siblingItem
.equals(treeItem
)) {
540 if (treeItemIndex
== 0) {
541 previousItem
= getPreviousItem(parentItem
, index
- 1);
542 } else if (treeItemIndex
== 1) {
543 TreeItem firstChild
= parentItem
.getItem(0);
544 previousItem
= getLastVisibleItem(firstChild
);
545 previousItem
= getPreviousItem(previousItem
, index
- 1);
547 previousItem
= getPreviousItem(getLastVisibleItem(parentItem
.getItem(treeItemIndex
- 1)),
551 // It is a root item. May be there are some previous root items.
552 Tree tree
= treeItem
.getParent();
553 int treeItemIndex
= 0;
554 for (TreeItem siblingItem
: tree
.getItems()) {
555 if (siblingItem
.equals(treeItem
)) {
560 if (treeItemIndex
== 0) {
561 previousItem
= treeItem
;
562 } else if (treeItemIndex
== 1) {
563 TreeItem firstRoot
= tree
.getItem(0);
564 if (firstRoot
.getExpanded()) {
565 previousItem
= getLastVisibleItem(firstRoot
);
567 previousItem
= firstRoot
;
569 previousItem
= getPreviousItem(previousItem
, index
- 1);
571 previousItem
= tree
.getItem(treeItemIndex
- index
);
579 * Returns the last visible {@link TreeItem} in the Treeviewer associated with this TreeRuler.
581 * @return the last visible TreeItem in the Treeviewer associated with this TreeRuler.
583 private TreeItem
getLastVisibleItem() {
584 int rootChildren
= fTreeViewer
.getTree().getItemCount();
585 return getLastVisibleItem(fTreeViewer
.getTree().getItem(rootChildren
- 1));
589 * Returns the last visible child of the given {@link TreeItem} in the Treeviewer associated with this
593 * teh given TreeItem.
594 * @return the last visible child of the given TreeItem in the Treeviewer associated with this TreeRuler.
596 private TreeItem
getLastVisibleItem(TreeItem item
) {
597 TreeItem lastVisibleItem
= null;
598 int directChildren
= item
.getItemCount();
599 if (directChildren
== 0 || !item
.getExpanded()) {
600 lastVisibleItem
= item
;
602 TreeItem lastDirectChildren
= item
.getItem(directChildren
- 1);
603 if (lastDirectChildren
.getData() == null) {
604 lastVisibleItem
= item
;
605 } else if (lastDirectChildren
.getExpanded()) {
606 lastVisibleItem
= getLastVisibleItem(lastDirectChildren
);
608 lastVisibleItem
= lastDirectChildren
;
611 return lastVisibleItem
;
615 * From a list of {@link Diff}s, returns the diffs which are not filtered by a filter of the given list of
616 * {@link IDifferenceFilter}.
618 * @param unfilteredDiffs
619 * the given list of unfiltered diffs.
621 * the given list of IDifferenceFilter.
622 * @return A filtered list of diffs.
624 protected Collection
<?
extends Diff
> filteredDiffs(Collection
<?
extends Diff
> unfilteredDiffs
,
625 Collection
<IDifferenceFilter
> filters
) {
626 if (filters
!= null) {
627 List
<Diff
> filteredDiffs
= Lists
.newArrayList(unfilteredDiffs
);
628 for (IDifferenceFilter filter
: filters
) {
629 for (Diff unfilteredDiff
: unfilteredDiffs
) {
630 if (filter
.getPredicateWhenSelected().apply(unfilteredDiff
)) {
631 filteredDiffs
.remove(unfilteredDiff
);
635 return filteredDiffs
;
637 return unfilteredDiffs
;