/*******************************************************************************
* Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch>
*
* 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
*******************************************************************************/
package org.eclipse.egit.ui.internal.commit;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.LineNumberRulerColumn;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.themes.ColorUtil;
/**
* A {@link LineNumberRulerColumn} specialized for a viewer showing a
* {@link DiffDocument}: it can display logical line numbers corresponding to
* the shown diff hunks (both old and new), or alternatively the physical line
* numbers of the unified diff itself.
*/
public class OldNewLogicalLineNumberRulerColumn extends LineNumberRulerColumn {
/** Container for assembling the actual line number columns. */
private final CompositeRuler composite = new CompositeRuler(0);
// Must not have any gap, otherwise mouse wheel scrolling won't work when
// the mouse cursor happens to be exactly on the gap.
/** Standard physical line numbers for plain display. */
private final LineNumberRulerColumn plainLines = new LineNumberRulerColumn() {
@Override
public Control createControl(CompositeRuler parentRuler,
Composite parentControl) {
return addMenuListener(
super.createControl(parentRuler, parentControl));
}
};
/**
* Ruler for the old line numbers; draws a vertical line on its right edge
* in order to get a visual separation of old and new line numbers.
* <p>
* Note that the rulers are always on the left side of the viewer, even in
* an RTL layout. They are also always ordered as given by their indices in
* a CompositeRuler -- there is no logic anywhere that would flip the order
* when the layout direction is changed. Thus drawing the line always on the
* right edge of the left ruler is correct in all cases.
* </p>
*/
private final LineNumberRulerColumn oldLines = new LogicalLineNumberRulerColumn(
DiffEntry.Side.OLD) {
private ResourceManager resourceManager;
private Color lineColor;
@Override
public Control createControl(CompositeRuler parentRuler,
Composite parentControl) {
return addMenuListener(
super.createControl(parentRuler, parentControl));
}
@Override
public int getWidth() {
// Add space for the line plus one empty pixel on each side of the
// line.
return super.getWidth() + 3;
}
@Override
protected void paintLine(int line, int y, int lineHeight, GC gc,
Display display) {
super.paintLine(line, y, lineHeight, gc, display);
// Draw a vertical line to separate old numbers from the new ones.
int x = super.getWidth() + 1;
if (lineColor == null) {
if (resourceManager == null) {
resourceManager = new LocalResourceManager(
JFaceResources.getResources());
}
lineColor = resourceManager.createColor(
ColorUtil.blend(gc.getForeground().getRGB(),
gc.getBackground().getRGB(), 60));
}
Color foreground = gc.getForeground();
// Needs to redraw the whole line each time, otherwise it'll have
// gaps when word-wrapping is on. There doesn't seem to be any hook
// available to hook into drawing after all line numbers have been
// drawn.
Rectangle bounds = super.getControl().getBounds();
gc.setForeground(lineColor);
gc.drawLine(x * zoom / 100, 0, x * zoom / 100,
bounds.height * zoom / 100);
gc.setForeground(foreground);
}
@Override
public void setForeground(Color foreground) {
lineColor = null;
super.setForeground(foreground);
}
@Override
public void setBackground(Color background) {
lineColor = null;
super.setBackground(background);
}
@Override
protected void handleDispose() {
super.handleDispose();
if (resourceManager != null) {
resourceManager.dispose();
resourceManager = null;
}
lineColor = null;
}
};
/** Ruler for the new line numbers. */
private final LineNumberRulerColumn newLines = new LogicalLineNumberRulerColumn(
DiffEntry.Side.NEW) {
@Override
public Control createControl(CompositeRuler parentRuler,
Composite parentControl) {
return addMenuListener(
super.createControl(parentRuler, parentControl));
}
};
/**
* Current display mode. If {@code true}, showing only physical line
* numbers, otherwise showing both old and new logical line numbers.
*/
private boolean plain;
/**
* We need our own listener for SWT.MenuDetect. The framework does propagate
* the parent's listener to children, but our own CompositeRuler then
* propagates its own to _its_ children. And that one looks for a menu set
* on its own control. However, the text editor framework sets the menu only
* on the outer CompositeRuler, and thus the context menu will not appear on
* our own columns unless we set a MenuDetect listener ourselves.
*/
private Listener menuListener;
/**
* Creates a new {@link OldNewLogicalLineNumberRulerColumn} showing both old
* and new logical line numbers.
*/
public OldNewLogicalLineNumberRulerColumn() {
this(false);
}
/**
* Creates a new {@link OldNewLogicalLineNumberRulerColumn}.
*
* @param plain
* {@code true} to show physical line numbers only, or
* {@code false} to show both old and new logical line numbers.
*/
public OldNewLogicalLineNumberRulerColumn(boolean plain) {
this.plain = plain;
if (!plain) {
composite.addDecorator(0, oldLines);
composite.addDecorator(1, newLines);
} else {
composite.addDecorator(0, plainLines);
}
}
@Override
public void setModel(IAnnotationModel model) {
composite.setModel(model);
}
@Override
public void redraw() {
composite.immediateUpdate();
}
@Override
public Control createControl(CompositeRuler parentRuler,
Composite parentControl) {
menuListener = (event) -> {
if (event.type == SWT.MenuDetect) {
Menu contextMenu = parentControl.getMenu();
if (contextMenu != null) {
contextMenu.setLocation(event.x, event.y);
contextMenu.setVisible(true);
}
}
};
return composite.createControl(parentControl,
parentRuler.getTextViewer());
}
private Control addMenuListener(Control control) {
if (menuListener != null) {
control.addListener(SWT.MenuDetect, menuListener);
}
return control;
}
@Override
public Control getControl() {
return composite.getControl();
}
@Override
public int getWidth() {
return composite.getWidth();
}
@Override
public void setFont(Font font) {
plainLines.setFont(font);
oldLines.setFont(font);
newLines.setFont(font);
}
@Override
public void setForeground(Color foreground) {
super.setForeground(foreground);
plainLines.setForeground(foreground);
oldLines.setForeground(foreground);
newLines.setForeground(foreground);
}
@Override
public void setBackground(Color background) {
super.setBackground(background);
plainLines.setBackground(background);
oldLines.setBackground(background);
newLines.setBackground(background);
}
/**
* Tells whether the column is currently plain, showing only physical line
* numbers.
*
* @return {@code true} if only physical line numbers are shown;
* {@code false} if old and new logical line numbers are shown
*/
public boolean isPlain() {
return plain;
}
/**
* Switches the mode of the {@link OldNewLogicalLineNumberRulerColumn}.
*
* @param plain
* {@code true} to show only physical line numbers; {@code false}
* to show old and new logical line numbers.
*/
public void setPlain(boolean plain) {
if (this.plain != plain) {
this.plain = plain;
if (!plain) {
composite.removeDecorator(plainLines);
composite.addDecorator(0, oldLines);
composite.addDecorator(1, newLines);
} else {
composite.removeDecorator(oldLines);
composite.removeDecorator(newLines);
composite.addDecorator(0, plainLines);
}
}
}
}