[413225] Fix Consequences are not properly highlighted
[EMFCompare2.git] / plugins / org.eclipse.emf.compare.ide.ui / src / org / eclipse / emf / compare / ide / ui / internal / structuremergeviewer / EMFCompareDiffTreeRuler.java
blob403e05f2413746fd39eb8b5dabbc0258d7555ad2
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
7 *
8 * Contributors:
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;
23 import java.util.Map;
24 import java.util.Set;
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;
59 /**
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;
126 * Constructor.
128 * @param parent
129 * the control's parent.
130 * @param style
131 * the style of the control to construct.
132 * @param width
133 * the control's width.
134 * @param treeViewer
135 * the TreeViewer associated with this control.
136 * @param config
137 * the configuration for this control.
139 EMFCompareDiffTreeRuler(Composite parent, int style, int width, TreeViewer treeViewer,
140 CompareConfiguration config) {
141 super(parent, style);
142 fWidth = width;
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) {
162 handlePaintEvent(e);
165 addPaintListener(paintListener);
167 mouseClickListener = new MouseListener() {
169 public void mouseUp(MouseEvent e) {
170 handleMouseClickEvent(e);
173 public void mouseDown(MouseEvent e) {
174 // Do nothing.
177 public void mouseDoubleClick(MouseEvent e) {
178 // Do nothing.
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) {
198 // Do nothing.
201 public void mouseEnter(MouseEvent e) {
202 // Do nothing.
205 addMouseTrackListener(mouseTrackListener);
209 * Compute consequences (required and unmergeable differences) when selection changed occurs.
211 * @param event
212 * the SelectionChangedEvent event.
214 public void selectionChanged(SelectionChangedEvent event) {
215 clearAllData();
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() {
236 clearAllData();
237 if (selectedDiff != null) {
238 Boolean leftToRight = (Boolean)fConfiguration.getProperty(EMFCompareConstants.MERGE_WAY);
239 boolean ltr = false;
240 if (leftToRight == null || leftToRight.booleanValue()) {
241 ltr = true;
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);
248 } else {
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.
259 * @param 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.
272 * @param item
273 * the given tree item.
274 * @param diffs
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);
294 * Clear all data.
296 private void clearAllData() {
297 requires.clear();
298 unmergeables.clear();
299 diffItems.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.
316 * @param e
317 * 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.
340 * @param e
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);
354 redraw();
355 return;
361 * Handles the mouse move event.
363 * @param e
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);
371 break;
374 if (cursor != lastCursor) {
375 setCursor(cursor);
376 lastCursor = cursor;
381 * Handles the mouse hover event.
383 * @param e
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();
392 break;
395 setToolTipText(overview);
399 * Create an annotation in the tree ruler.
401 * @param e
402 * the PaintEvent.
403 * @param diff
404 * the Diff for which we want to create the annotation.
405 * @param treeItem
406 * the tree item associated with the diff.
407 * @param fill
408 * the annotation's fill color.
409 * @param border
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);
414 if (item != null) {
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,
427 border);
428 annotationsData.put(rect, treeItem);
433 * Returns the full tree path of the given tree item.
435 * @param 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
453 * enabled.
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();
462 return false;
466 * Draw an annotation (a Rectangle) on this tree ruler.
468 * @param gc
469 * the swt GC.
470 * @param x
471 * the x coordinate of the origin of the annotation.
472 * @param y
473 * the y coordinate of the origin of the annotation.
474 * @param w
475 * the width of the annotation.
476 * @param h
477 * the height of the annotation.
478 * @param fill
479 * the annotation's fill color.
480 * @param border
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);
491 return rect;
495 * Returns, for the given tree item, the deepest visible {@link TreeItem} in the Treeviewer associated
496 * with this TreeRuler.
498 * @param currentItem
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);
512 } else {
513 item = getDeepestVisibleTreeItem(parent, parent);
516 return item;
520 * Get the previous item of the given {@link TreeItem}.
522 * @param treeItem
523 * the given {@link TreeItem}.
524 * @param index
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;
530 if (index > 0) {
531 TreeItem parentItem = treeItem.getParentItem();
532 if (parentItem != null) {
533 int treeItemIndex = 0;
534 for (TreeItem siblingItem : parentItem.getItems()) {
535 if (siblingItem.equals(treeItem)) {
536 break;
538 treeItemIndex++;
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);
546 } else {
547 previousItem = getPreviousItem(getLastVisibleItem(parentItem.getItem(treeItemIndex - 1)),
548 index - 1);
550 } else {
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)) {
556 break;
558 treeItemIndex++;
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);
566 } else {
567 previousItem = firstRoot;
569 previousItem = getPreviousItem(previousItem, index - 1);
570 } else {
571 previousItem = tree.getItem(treeItemIndex - index);
575 return previousItem;
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
590 * TreeRuler.
592 * @param item
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;
601 } else {
602 TreeItem lastDirectChildren = item.getItem(directChildren - 1);
603 if (lastDirectChildren.getData() == null) {
604 lastVisibleItem = item;
605 } else if (lastDirectChildren.getExpanded()) {
606 lastVisibleItem = getLastVisibleItem(lastDirectChildren);
607 } else {
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.
620 * @param filters
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;