blob: b9e6375080db9d61c74208a4cc104bbd972e3399 [file] [log] [blame]
//Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.diff;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.LineCharacter;
import net.codemirror.lib.ScrollInfo;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** Displays overview of all edits and comments in this file. */
class OverviewBar extends Composite implements ClickHandler {
interface Binder extends UiBinder<HTMLPanel, OverviewBar> {}
private static final Binder uiBinder = GWT.create(Binder.class);
interface Style extends CssResource {
String gutter();
String halfGutter();
String comment();
String draft();
String insert();
String delete();
String viewportDrag();
}
enum MarkType {
COMMENT, DRAFT, INSERT, DELETE, EDIT
}
@UiField Style style;
@UiField Label viewport;
private final List<MarkHandle> diff;
private final Set<MarkHandle> comments;
private CodeMirror cmB;
private boolean dragging;
private int startY;
private double ratio;
OverviewBar() {
initWidget(uiBinder.createAndBindUi(this));
diff = new ArrayList<>();
comments = new HashSet<>();
addDomHandler(this, ClickEvent.getType());
}
void init(CodeMirror cmB) {
this.cmB = cmB;
}
void refresh() {
update(cmB.getScrollInfo());
}
void update(ScrollInfo si) {
double viewHeight = si.getClientHeight();
double r = ratio(si);
com.google.gwt.dom.client.Style style = viewport.getElement().getStyle();
style.setTop(si.getTop() * r, Unit.PX);
style.setHeight(Math.max(10, viewHeight * r), Unit.PX);
getElement().getStyle().setHeight(viewHeight, Unit.PX);
for (MarkHandle info : diff) {
info.position(r);
}
for (MarkHandle info : comments) {
info.position(r);
}
}
@Override
protected void onUnload() {
super.onUnload();
if (dragging) {
DOM.releaseCapture(viewport.getElement());
}
}
@Override
public void onClick(ClickEvent e) {
if (e.getY() < viewport.getElement().getOffsetTop()) {
CodeMirror.handleVimKey(cmB, "<PageUp>");
} else {
CodeMirror.handleVimKey(cmB, "<PageDown>");
}
cmB.focus();
}
@UiHandler("viewport")
void onMouseDown(MouseDownEvent e) {
if (cmB != null) {
dragging = true;
ratio = ratio(cmB.getScrollInfo());
startY = e.getY();
viewport.addStyleName(style.viewportDrag());
DOM.setCapture(viewport.getElement());
e.preventDefault();
e.stopPropagation();
}
}
@UiHandler("viewport")
void onMouseMove(MouseMoveEvent e) {
if (dragging) {
int y = e.getRelativeY(getElement()) - startY;
cmB.scrollToY(Math.max(0, y / ratio));
e.preventDefault();
e.stopPropagation();
}
}
@UiHandler("viewport")
void onMouseUp(MouseUpEvent e) {
if (dragging) {
dragging = false;
DOM.releaseCapture(viewport.getElement());
viewport.removeStyleName(style.viewportDrag());
e.preventDefault();
e.stopPropagation();
}
}
private double ratio(ScrollInfo si) {
double barHeight = si.getClientHeight();
double contentHeight = si.getHeight();
return barHeight / contentHeight;
}
MarkHandle add(CodeMirror cm, int line, int height, MarkType type) {
MarkHandle mark = new MarkHandle(cm, line, height);
switch (type) {
case COMMENT:
mark.addStyleName(style.comment());
comments.add(mark);
break;
case DRAFT:
mark.addStyleName(style.draft());
mark.getElement().setInnerText("*");
comments.add(mark);
break;
case INSERT:
mark.addStyleName(style.insert());
diff.add(mark);
break;
case DELETE:
mark.addStyleName(style.delete());
diff.add(mark);
break;
case EDIT:
mark.edit = DOM.createDiv();
mark.edit.setClassName(style.halfGutter());
mark.getElement().appendChild(mark.edit);
mark.addStyleName(style.insert());
diff.add(mark);
break;
}
if (cmB != null) {
mark.position(ratio(cmB.getScrollInfo()));
}
((HTMLPanel) getWidget()).add(mark);
return mark;
}
void clearDiffMarkers() {
for (MarkHandle mark : diff) {
mark.removeFromParent();
}
diff.clear();
}
class MarkHandle extends Widget implements ClickHandler {
private static final int MIN_HEIGHT = 3;
private final CodeMirror cm;
private final int line;
private final int height;
private Element edit;
MarkHandle(CodeMirror cm, int line, int height) {
this.cm = cm;
this.line = line;
this.height = height;
setElement((Element)(DOM.createDiv()));
setStyleName(style.gutter());
addDomHandler(this, ClickEvent.getType());
}
void position(double ratio) {
double y = cm.heightAtLine(line, "local");
getElement().getStyle().setTop(y * ratio, Unit.PX);
if (height > 1) {
double e = cm.heightAtLine(line + height, "local");
double h = Math.max(MIN_HEIGHT, (e - y) * ratio);
getElement().getStyle().setHeight(h, Unit.PX);
if (edit != null) {
edit.getStyle().setHeight(h, Unit.PX);
}
}
}
@Override
public void onClick(ClickEvent e) {
if (height == 1 || !visible()) {
e.stopPropagation();
double y = cm.heightAtLine(line, "local");
double viewport = cm.getScrollInfo().getClientHeight();
cm.setCursor(LineCharacter.create(line));
cm.scrollToY(y - 0.5 * viewport);
cm.focus();
}
}
private boolean visible() {
int markT = getElement().getOffsetTop();
int markE = markT + getElement().getOffsetHeight();
int viewT = viewport.getElement().getOffsetTop();
int viewE = viewT + viewport.getElement().getOffsetHeight();
return (viewT <= markT && markT < viewE) // mark top within viewport
|| (viewT <= markE && markE < viewE) // mark end within viewport
|| (markT <= viewT && viewE <= markE); // mark contains viewport
}
void remove() {
removeFromParent();
comments.remove(this);
}
}
}